mirror of
https://github.com/securego/gosec.git
synced 2026-01-15 01:33:41 +08:00
Compare commits
10 Commits
v2.22.11
...
8a5404eabf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a5404eabf | ||
|
|
0f6f21cb3f | ||
|
|
717706e815 | ||
|
|
082deb6cee | ||
|
|
095d529a90 | ||
|
|
c073629009 | ||
|
|
538a05cc5d | ||
|
|
25804378cd | ||
|
|
872b33106c | ||
|
|
dcf93a8b8b |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
go-version: ${{ matrix.version.go-version }}
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
go-version: "1.25.5"
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
34
README.md
34
README.md
@@ -76,6 +76,38 @@ jobs:
|
||||
args: ./...
|
||||
```
|
||||
|
||||
#### Scanning Projects with Private Modules
|
||||
|
||||
If your project imports private Go modules, you need to configure authentication so that `gosec` can fetch the dependencies. Set the following environment variables in your workflow:
|
||||
|
||||
- `GOPRIVATE`: A comma-separated list of module path prefixes that should be considered private (e.g., `github.com/your-org/*`).
|
||||
- `GITHUB_AUTHENTICATION_TOKEN`: A GitHub token with read access to your private repositories.
|
||||
|
||||
```yaml
|
||||
name: Run Gosec
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: on
|
||||
GOPRIVATE: github.com/your-org/*
|
||||
GITHUB_AUTHENTICATION_TOKEN: ${{ secrets.PRIVATE_REPO_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v3
|
||||
- name: Run Gosec Security Scanner
|
||||
uses: securego/gosec@master
|
||||
with:
|
||||
args: ./...
|
||||
```
|
||||
|
||||
### Integrating with code scanning
|
||||
|
||||
You can [integrate third-party code analysis tools](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/integrating-with-code-scanning) with GitHub code scanning by uploading data as SARIF files.
|
||||
@@ -140,6 +172,8 @@ directory you can supply `./...` as the input argument.
|
||||
- G112: Potential slowloris attack
|
||||
- G114: Use of net/http serve function that has no support for setting timeouts
|
||||
- G115: Potential integer overflow when converting between integer types
|
||||
- G116: Detect Trojan Source attacks using bidirectional Unicode control characters
|
||||
- G117: Potential exposure of secrets via JSON marshaling
|
||||
- G201: SQL query construction using format string
|
||||
- G202: SQL query construction using string concatenation
|
||||
- G203: Use of unescaped data in HTML templates
|
||||
|
||||
@@ -10,7 +10,7 @@ inputs:
|
||||
|
||||
runs:
|
||||
using: "docker"
|
||||
image: "docker://securego/gosec:2.22.10"
|
||||
image: "docker://securego/gosec:2.22.11"
|
||||
args:
|
||||
- ${{ inputs.args }}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
upperUnbounded
|
||||
unbounded
|
||||
upperBounded
|
||||
bounded
|
||||
)
|
||||
|
||||
const maxDepth = 20
|
||||
@@ -182,6 +183,22 @@ func runSliceBounds(pass *analysis.Pass) (interface{}, error) {
|
||||
delete(issues, instr)
|
||||
}
|
||||
}
|
||||
case bounded:
|
||||
switch tinstr := instr.(type) {
|
||||
case *ssa.Slice:
|
||||
lower, upper := extractSliceBounds(tinstr)
|
||||
if isSliceInsideBounds(value, value, lower, upper) {
|
||||
delete(issues, instr)
|
||||
}
|
||||
case *ssa.IndexAddr:
|
||||
indexValue, err := extractIntValue(tinstr.Index.String())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if indexValue == value {
|
||||
delete(issues, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if nestedIfInstr, ok := instr.(*ssa.If); ok {
|
||||
for _, nestedBlock := range nestedIfInstr.Block().Succs {
|
||||
@@ -258,23 +275,24 @@ func trackSliceBounds(depth int, sliceCap int, slice ssa.Node, violations *[]ssa
|
||||
|
||||
func extractIntValueIndexAddr(refinstr *ssa.IndexAddr, sliceCap int) (int, error) {
|
||||
var indexIncr, sliceIncr int
|
||||
idxRefs := refinstr.Index.Referrers()
|
||||
if idxRefs == nil {
|
||||
return 0, errors.New("no found")
|
||||
}
|
||||
for _, instr := range *idxRefs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.BinOp:
|
||||
_, index, err := extractBinOpBound(instr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch instr.Op {
|
||||
case token.LSS:
|
||||
indexIncr--
|
||||
}
|
||||
|
||||
for _, block := range refinstr.Block().Preds {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.BinOp:
|
||||
_, index, err := extractBinOpBound(instr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch instr.Op {
|
||||
case token.LSS:
|
||||
indexIncr--
|
||||
}
|
||||
|
||||
if !isSliceIndexInsideBounds(sliceCap+sliceIncr, index+indexIncr) {
|
||||
return index, nil
|
||||
}
|
||||
if !isSliceIndexInsideBounds(sliceCap+sliceIncr, index+indexIncr) {
|
||||
return index, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,19 +340,28 @@ func checkAllSlicesBounds(depth int, sliceCap int, slice *ssa.Slice, violations
|
||||
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 {
|
||||
refs := []ssa.Instruction{}
|
||||
if call.Referrers() != nil {
|
||||
refs = append(refs, *call.Referrers()...)
|
||||
}
|
||||
depth := 0
|
||||
for len(refs) > 0 && depth < maxDepth {
|
||||
newrefs := []ssa.Instruction{}
|
||||
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
|
||||
}
|
||||
newrefs = append(newrefs, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
refs = newrefs
|
||||
depth++
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
@@ -363,6 +390,8 @@ func invBound(bound bound) bound {
|
||||
return unbounded
|
||||
case unbounded:
|
||||
return upperBounded
|
||||
case bounded:
|
||||
return bounded
|
||||
default:
|
||||
return unbounded
|
||||
}
|
||||
@@ -386,7 +415,7 @@ func extractBinOpBound(binop *ssa.BinOp) (bound, int, error) {
|
||||
case token.GTR, token.GEQ:
|
||||
return lowerUnbounded, value, nil
|
||||
case token.EQL:
|
||||
return upperBounded, value, nil
|
||||
return bounded, value, nil
|
||||
case token.NEQ:
|
||||
return unbounded, value, nil
|
||||
}
|
||||
@@ -407,7 +436,7 @@ func extractBinOpBound(binop *ssa.BinOp) (bound, int, error) {
|
||||
case token.GTR, token.GEQ:
|
||||
return upperUnbounded, value, nil
|
||||
case token.EQL:
|
||||
return upperBounded, value, nil
|
||||
return bounded, value, nil
|
||||
case token.NEQ:
|
||||
return unbounded, value, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ var _ GenAIClient = (*openaiWrapper)(nil)
|
||||
|
||||
type OpenAIConfig struct {
|
||||
Model string
|
||||
APIKey string
|
||||
APIKey string `json:"-"`
|
||||
BaseURL string
|
||||
MaxTokens int
|
||||
Temperature float64
|
||||
|
||||
@@ -118,6 +118,11 @@ var idWeaknesses = map[string]*Weakness{
|
||||
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)",
|
||||
},
|
||||
"499": {
|
||||
ID: "499",
|
||||
Description: "The code contains a class with sensitive data, but the class does not explicitly deny serialization. The data can be accessed by serializing the class through another class.",
|
||||
Name: "Serializable Class Containing Sensitive Data",
|
||||
},
|
||||
"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.",
|
||||
|
||||
22
go.mod
22
go.mod
@@ -7,16 +7,16 @@ require (
|
||||
github.com/gookit/color v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mozilla/tls-observatory v0.0.0-20250923143331-eef96233227e
|
||||
github.com/onsi/ginkgo/v2 v2.27.2
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/openai/openai-go/v3 v3.8.1
|
||||
github.com/onsi/ginkgo/v2 v2.27.3
|
||||
github.com/onsi/gomega v1.38.3
|
||||
github.com/openai/openai-go/v3 v3.15.0
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/text v0.31.0
|
||||
golang.org/x/tools v0.39.0
|
||||
google.golang.org/genai v1.37.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/text v0.32.0
|
||||
golang.org/x/tools v0.40.0
|
||||
google.golang.org/genai v1.40.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -47,10 +47,10 @@ require (
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
|
||||
48
go.sum
48
go.sum
@@ -306,13 +306,13 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW
|
||||
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8=
|
||||
github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/openai/openai-go/v3 v3.8.1 h1:b+YWsmwqXnbpSHWQEntZAkKciBZ5CJXwL68j+l59UDg=
|
||||
github.com/openai/openai-go/v3 v3.8.1/go.mod h1:UOpNxkqC9OdNXNUfpNByKOtB4jAL0EssQXq5p8gO0Xs=
|
||||
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
|
||||
github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||
github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo=
|
||||
github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
@@ -443,8 +443,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -478,8 +478,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -512,8 +512,8 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -528,8 +528,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -570,18 +570,18 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -633,8 +633,8 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
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=
|
||||
@@ -663,8 +663,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
|
||||
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genai v1.37.0 h1:dgp71k1wQ+/+APdZrN3LFgAGnVnr5IdTF1Oj0Dg+BQc=
|
||||
google.golang.org/genai v1.37.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
|
||||
google.golang.org/genai v1.40.0 h1:kYxyQSH+vsib8dvsgyLJzsVEIv5k3ZmHJyVqdvGncmc=
|
||||
google.golang.org/genai v1.40.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
|
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
|
||||
@@ -68,6 +68,7 @@ var ruleToCWE = map[string]string{
|
||||
"G114": "676",
|
||||
"G115": "190",
|
||||
"G116": "838",
|
||||
"G117": "499",
|
||||
"G201": "89",
|
||||
"G202": "89",
|
||||
"G203": "79",
|
||||
|
||||
@@ -90,6 +90,12 @@ func TryResolve(n ast.Node, c *Context) bool {
|
||||
return resolveCallExpr(node, c)
|
||||
case *ast.BinaryExpr:
|
||||
return resolveBinExpr(node, c)
|
||||
case *ast.KeyValueExpr:
|
||||
return TryResolve(node.Key, c) && TryResolve(node.Value, c)
|
||||
case *ast.IndexExpr:
|
||||
return TryResolve(node.X, c)
|
||||
case *ast.SliceExpr:
|
||||
return TryResolve(node.X, c)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ func NewNoErrorCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
whitelist.Add("io.PipeWriter", "CloseWithError")
|
||||
whitelist.Add("hash.Hash", "Write")
|
||||
whitelist.Add("os", "Unsetenv")
|
||||
whitelist.Add("rand", "Read")
|
||||
|
||||
if configured, ok := conf[id]; ok {
|
||||
if whitelisted, ok := configured.(map[string]interface{}); ok {
|
||||
|
||||
@@ -229,6 +229,8 @@ func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error
|
||||
return r.matchValueSpec(node, ctx)
|
||||
case *ast.BinaryExpr:
|
||||
return r.matchEqualityCheck(node, ctx)
|
||||
case *ast.CompositeLit:
|
||||
return r.matchCompositeLit(node, ctx)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -334,6 +336,44 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *credentials) matchCompositeLit(lit *ast.CompositeLit, ctx *gosec.Context) (*issue.Issue, error) {
|
||||
for _, elt := range lit.Elts {
|
||||
if kv, ok := elt.(*ast.KeyValueExpr); ok {
|
||||
// Check if the key matches the credential pattern (struct field name or map string literal key)
|
||||
matchedKey := false
|
||||
if ident, ok := kv.Key.(*ast.Ident); ok {
|
||||
if r.pattern.MatchString(ident.Name) {
|
||||
matchedKey = true
|
||||
}
|
||||
}
|
||||
if keyStr, err := gosec.GetString(kv.Key); err == nil {
|
||||
if r.pattern.MatchString(keyStr) {
|
||||
matchedKey = true
|
||||
}
|
||||
}
|
||||
|
||||
// If key matches, check value for high entropy (generic credential warning)
|
||||
if matchedKey {
|
||||
if val, err := gosec.GetString(kv.Value); err == nil {
|
||||
if r.ignoreEntropy || r.isHighEntropyString(val) {
|
||||
return ctx.NewIssue(lit, r.ID(), r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separately check value for specific secret patterns (regardless of key)
|
||||
if val, err := gosec.GetString(kv.Value); err == nil {
|
||||
if r.ignoreEntropy || r.isHighEntropyString(val) {
|
||||
if ok, patternName := r.isSecretPattern(val); ok {
|
||||
return ctx.NewIssue(lit, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewHardcodedCredentials attempts to find high entropy string constants being
|
||||
// assigned to variables that appear to be related to credentials.
|
||||
func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
@@ -390,5 +430,5 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No
|
||||
Confidence: issue.Low,
|
||||
Severity: issue.High,
|
||||
},
|
||||
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil)}
|
||||
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil), (*ast.CompositeLit)(nil)}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
|
||||
{"G112", "Detect ReadHeaderTimeout not configured as a potential risk", NewSlowloris},
|
||||
{"G114", "Use of net/http serve function that has no support for setting timeouts", NewHTTPServeWithoutTimeouts},
|
||||
{"G116", "Detect Trojan Source attacks using bidirectional Unicode characters", NewTrojanSource},
|
||||
{"G117", "Potential exposure of secrets via JSON marshaling", NewSecretSerialization},
|
||||
|
||||
// injection
|
||||
{"G201", "SQL query construction using format string", NewSQLStrFormat},
|
||||
|
||||
@@ -111,6 +111,10 @@ var _ = Describe("gosec rules", func() {
|
||||
runner("G116", testutils.SampleCodeG116)
|
||||
})
|
||||
|
||||
It("should detect exported struct fields that may contain secrets and are JSON serializable", func() {
|
||||
runner("G117", testutils.SampleCodeG117)
|
||||
})
|
||||
|
||||
It("should detect sql injection via format strings", func() {
|
||||
runner("G201", testutils.SampleCodeG201)
|
||||
})
|
||||
|
||||
123
rules/secret_serialization.go
Normal file
123
rules/secret_serialization.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/securego/gosec/v2"
|
||||
"github.com/securego/gosec/v2/issue"
|
||||
)
|
||||
|
||||
type secretSerialization struct {
|
||||
issue.MetaData
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *secretSerialization) ID() string {
|
||||
return r.MetaData.ID
|
||||
}
|
||||
|
||||
func (r *secretSerialization) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) {
|
||||
field, ok := n.(*ast.Field)
|
||||
if !ok || len(field.Names) == 0 {
|
||||
return nil, nil // skip embedded (anonymous) fields
|
||||
}
|
||||
|
||||
// Parse the JSON tag to determine behavior
|
||||
omitted := false
|
||||
jsonKey := ""
|
||||
|
||||
if field.Tag != nil {
|
||||
if tagVal, err := strconv.Unquote(field.Tag.Value); err == nil && tagVal != "" {
|
||||
st := reflect.StructTag(tagVal)
|
||||
if tag := st.Get("json"); tag != "" {
|
||||
if tag == "-" {
|
||||
omitted = true
|
||||
} else {
|
||||
// "name,omitempty" -> "name"
|
||||
// "-," -> "-" (A field literally named "-")
|
||||
parts := strings.SplitN(tag, ",", 2)
|
||||
jsonKey = parts[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if omitted {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Iterate over all names in this field definition
|
||||
// e.g., type T struct { Pass, Salt string }
|
||||
isSensitiveType := false
|
||||
switch t := field.Type.(type) {
|
||||
case *ast.Ident:
|
||||
if t.Name == "string" {
|
||||
isSensitiveType = true
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "string" {
|
||||
isSensitiveType = true
|
||||
}
|
||||
case *ast.ArrayType:
|
||||
if star, ok := t.Elt.(*ast.StarExpr); ok {
|
||||
if ident, ok := star.X.(*ast.Ident); ok && ident.Name == "string" {
|
||||
isSensitiveType = true // []*string
|
||||
}
|
||||
} else if ident, ok := t.Elt.(*ast.Ident); ok {
|
||||
if ident.Name == "string" || ident.Name == "byte" {
|
||||
isSensitiveType = true // []string or []byte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isSensitiveType {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check each named exported field
|
||||
for _, nameIdent := range field.Names {
|
||||
fieldName := nameIdent.Name
|
||||
if fieldName == "_" || !ast.IsExported(fieldName) {
|
||||
continue
|
||||
}
|
||||
|
||||
effectiveKey := jsonKey
|
||||
if effectiveKey == "" {
|
||||
effectiveKey = fieldName
|
||||
}
|
||||
|
||||
if r.pattern.MatchString(fieldName) || r.pattern.MatchString(effectiveKey) {
|
||||
msg := fmt.Sprintf("Exported struct field %q (JSON key %q) matches secret pattern", fieldName, effectiveKey)
|
||||
return ctx.NewIssue(field, r.ID(), msg, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewSecretSerialization(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
patternStr := `(?i)\b((?:api|access|auth|bearer|client|oauth|private|refresh|session|jwt)[_-]?(?:key|secret|token)s?|password|passwd|pwd|pass|secret|cred|jwt)\b`
|
||||
|
||||
if val, ok := conf[id]; ok {
|
||||
if m, ok := val.(map[string]interface{}); ok {
|
||||
if p, ok := m["pattern"].(string); ok && p != "" {
|
||||
patternStr = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &secretSerialization{
|
||||
pattern: regexp.MustCompile(patternStr),
|
||||
MetaData: issue.MetaData{
|
||||
ID: id,
|
||||
What: "Exported struct field appears to be a secret and is not ignored by JSON marshaling",
|
||||
Severity: issue.Medium,
|
||||
Confidence: issue.Medium,
|
||||
},
|
||||
}, []ast.Node{(*ast.Field)(nil)}
|
||||
}
|
||||
@@ -364,7 +364,123 @@ const (
|
||||
func main() {
|
||||
fmt.Printf("%s\n", ConfigLearnerTokenAuth)
|
||||
}
|
||||
|
||||
|
||||
`}, 0, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type DBConfig struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ = DBConfig{
|
||||
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type DBConfig struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ = &DBConfig{
|
||||
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = struct{ Password string }{
|
||||
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = map[string]string{
|
||||
"password": "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = map[string]string{
|
||||
"apiKey": "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ = Config{
|
||||
Username: "admin",
|
||||
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type DBConfig struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ = DBConfig{ // #nosec G101
|
||||
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
|
||||
}
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
// Negatives
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = struct{ Password string }{
|
||||
Password: "secret", // low entropy
|
||||
}
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = map[string]string{
|
||||
"password": "secret", // low entropy
|
||||
}
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = struct{ Username string }{
|
||||
Username: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", // non-sensitive key
|
||||
}
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = []string{"f62e5bcda4fae4f82370da0c6f20697b8f8447ef"} // unkeyed – no trigger
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
}
|
||||
|
||||
@@ -430,6 +546,24 @@ func main() {
|
||||
if compareGoogleAPI == "AIzajtGS_aJGkoiAmSbXzu9I-1eytAi9Lrlh-vT" {
|
||||
fmt.Println(compareGoogleAPI)
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = struct{ SomeKey string }{
|
||||
SomeKey: "AKIAI44QH8DHBEXAMPLE",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
_ = map[string]string{
|
||||
"github_token": "ghp_iR54dhCYg9Tfmoywi9xLmmKZrrnAw438BYh3",
|
||||
}
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
}
|
||||
|
||||
@@ -142,6 +142,17 @@ func main() {
|
||||
b := createBuffer()
|
||||
b.WriteString("*bytes.Buffer")
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
import "crypto/rand"
|
||||
|
||||
func main() {
|
||||
b := make([]byte, 8)
|
||||
rand.Read(b)
|
||||
_ = b
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
} // it shouldn't return any errors because all method calls are whitelisted by default
|
||||
|
||||
|
||||
197
testutils/g117_samples.go
Normal file
197
testutils/g117_samples.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// testutils/g117_samples.go
|
||||
package testutils
|
||||
|
||||
import "github.com/securego/gosec/v2"
|
||||
|
||||
var SampleCodeG117 = []CodeSample{
|
||||
// Positive: match on field name (default JSON key)
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Password string
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
APIKey *string ` + "`json:\"api_key\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
PrivateKey []byte ` + "`json:\"private_key\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Positive: match on field name (explicit non-sensitive JSON key)
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Password string ` + "`json:\"text_field\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Positive: match on JSON key (non-sensitive field name)
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
SafeField string ` + "`json:\"api_key\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Positive: match on both
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Token string ` + "`json:\"auth_token\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Positive: snake/hyphen variants in JSON key
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Key string ` + "`json:\"access-key\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Positive: empty json tag part falls back to field name
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Secret string ` + "`json:\",omitempty\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Positive: plural forms
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
ApiTokens []string
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
RefreshTokens []string ` + "`json:\"refresh_tokens\"`" + `
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
AccessTokens []*string
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
CustomSecret string ` + "`json:\"my_custom_secret\"`" + `
|
||||
}
|
||||
`}, 1, func() gosec.Config {
|
||||
cfg := gosec.NewConfig()
|
||||
cfg.Set("G117", map[string]interface{}{
|
||||
"pattern": "(?i)custom[_-]?secret",
|
||||
})
|
||||
return cfg
|
||||
}()},
|
||||
|
||||
// Negative: json:"-" (omitted)
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Password string ` + "`json:\"-\"`" + `
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Negative: both field name and JSON key non-sensitive
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
UserID string ` + "`json:\"user_id\"`" + `
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Negative: unexported field
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
password string
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Negative: non-sensitive type (int) even with "token"
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
MaxTokens int
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Negative: non-secret plural slice (common FP like redaction placeholders)
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
RedactionTokens []string ` + "`json:\"redactionTokens,omitempty\"`" + `
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Negative: grouped fields, only one sensitive (should still flag the sensitive one)
|
||||
// Note: we expect 1 issue (for the sensitive field)
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Safe, Password string
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
|
||||
// Suppression: trailing line comment
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Password string // #nosec G117
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Suppression: line comment above field
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
// #nosec G117 -- false positive
|
||||
Password string
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
|
||||
// Suppression: trailing with justification
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
APIKey string ` + "`json:\"api_key\"`" + ` // #nosec G117 -- public key
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
}
|
||||
@@ -242,4 +242,40 @@ func main() {
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
}
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Safe OS-specific command selection using a hard-coded map and slice operations.
|
||||
// Closely matches the pattern in https://github.com/securego/gosec/issues/1199.
|
||||
// The command name and fixed arguments are fully resolved from constant composite literals,
|
||||
// even though the map key is runtime.GOOS (non-constant in analysis).
|
||||
func main() {
|
||||
commands := map[string][]string{
|
||||
"darwin": {"open"},
|
||||
"freebsd": {"xdg-open"},
|
||||
"linux": {"xdg-open"},
|
||||
"netbsd": {"xdg-open"},
|
||||
"openbsd": {"xdg-open"},
|
||||
"windows": {"cmd", "/c", "start"},
|
||||
}
|
||||
|
||||
platform := runtime.GOOS
|
||||
|
||||
cmdArgs := commands[platform]
|
||||
if cmdArgs == nil {
|
||||
return // unsupported platform
|
||||
}
|
||||
|
||||
exe := cmdArgs[0]
|
||||
args := cmdArgs[1:]
|
||||
|
||||
// No dynamic/tainted input; fixed args passed via ... expansion
|
||||
_ = exec.Command(exe, args...)
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
}
|
||||
|
||||
@@ -413,4 +413,39 @@ func main() {
|
||||
}
|
||||
|
||||
`}, 1, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
func main() {
|
||||
args := []any{"1"}
|
||||
switch len(args) - 1 {
|
||||
case 1:
|
||||
_ = args[1]
|
||||
}
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
{[]string{`
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
value := "1234567890"
|
||||
weight := []int{2, 3, 4, 5, 6, 7}
|
||||
wLen := len(weight)
|
||||
l := len(value) - 1
|
||||
addr := make([]any, 7)
|
||||
sum := 0
|
||||
weight[2] = 3
|
||||
for i := l; i >= 0; i-- {
|
||||
v := int(value[i] - '0')
|
||||
if v < 0 || v > 9 {
|
||||
fmt.Println("invalid number at column", i+1)
|
||||
break
|
||||
}
|
||||
addr[2] = v
|
||||
sum += v * weight[(l-i)%wLen]
|
||||
}
|
||||
fmt.Println(sum)
|
||||
}
|
||||
`}, 0, gosec.NewConfig()},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user