DEVELOPMENT.md
1.25+ (see go.mod)makemake testmake golangcigosec supports three implementation styles:
gosec.Rule) for node-level checks in rules/analysis.Analyzer) for whole-program context in analyzers/analyzers/ via taint.NewGosecAnalyzerrules/ (for example, use rules/unsafe.go as a simple template).Match logic.rules/rulelist.go.issue/issue.go (and add CWE data in cwe/data.go only if needed).testutils/rules/ or integration tests in analyzer_test.goanalyzers/.buildssa.Analyzer.ssautil.GetSSAResult(pass).[]*issue.Issue.analyzers/analyzerslist.go.issue/issue.go.analyzers/ and testutils/.Minimal skeleton:
package analyzers
import (
"fmt"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"github.com/securego/gosec/v2/internal/ssautil"
"github.com/securego/gosec/v2/issue"
)
func newMyAnalyzer(id, description string) *analysis.Analyzer {
return &analysis.Analyzer{
Name: id,
Doc: description,
Run: runMyAnalyzer,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
}
}
func runMyAnalyzer(pass *analysis.Pass) (interface{}, error) {
ssaResult, err := ssautil.GetSSAResult(pass)
if err != nil {
return nil, fmt.Errorf("getting SSA result: %w", err)
}
_ = ssaResult
var issues []*issue.Issue
return issues, nil
}
gosec taint analyzers track data flow from untrusted sources to dangerous sinks. Current taint rules include SQL injection, command injection, path traversal, SSRF, XSS, log injection, SMTP injection, server-side template injection, unsafe deserialization, and open redirect.
analyzers/ (for example analyzers/newvuln.go) with both:
Config (sources, sinks, optional sanitizers)taint.NewGosecAnalyzer(...)package analyzers
import (
"golang.org/x/tools/go/analysis"
"github.com/securego/gosec/v2/taint"
)
func NewVulnerability() taint.Config {
return taint.Config{
Sources: []taint.Source{
{Package: "net/http", Name: "Request", Pointer: true},
{Package: "os", Name: "Args", IsFunc: true},
},
Sinks: []taint.Sink{
{Package: "dangerous/package", Method: "DangerousFunc"},
},
}
}
func newNewVulnAnalyzer(id string, description string) *analysis.Analyzer {
config := NewVulnerability()
rule := NewVulnerabilityRule
rule.ID = id
rule.Description = description
return taint.NewGosecAnalyzer(&rule, &config)
}
analyzers/analyzerslist.go:var defaultAnalyzers = []AnalyzerDefinition{
// ... existing analyzers ...
{"G7XX", "Description of vulnerability", newNewVulnAnalyzer},
}
Add sample programs in testutils/g7xx_samples.go.
Add the analyzer test in analyzers/analyzers_test.go:
It("should detect your new vulnerability", func() {
runner("G7XX", testutils.SampleCodeG7XX)
})
Each taint analyzer keeps its configuration function in the same file as the analyzer. Reference implementations:
analyzers/sqlinjection.go (G701)analyzers/commandinjection.go (G702)analyzers/pathtraversal.go (G703)Sources define where untrusted data starts:
Package: import path (for example "net/http")Name: type or function name (for example "Request", "Getenv")Pointer: set true for pointer types (for example *http.Request)IsFunc: set true when the source is a function that returns tainted dataSinks define where tainted data must not reach:
PackageReceiver: method receiver type, empty for package functionsMethodPointer: whether receiver is a pointerCheckArgs: optional argument indexes to inspect; if omitted, all args are inspectedExample:
// For *sql.DB.Query, Args[1] is the query string.
{Package: "database/sql", Receiver: "DB", Method: "Query", Pointer: true, CheckArgs: []int{1}}
// Skip writer arg in fmt.Fprintf and check the rest.
{Package: "fmt", Method: "Fprintf", CheckArgs: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}
Sanitizers break taint flow after validation/escaping:
PackageReceiverMethodPointerIf data passes through a configured sanitizer, it is treated as safe for subsequent sinks.
| Source Type | Package | Type/Method | Pointer | IsFunc |
|---|---|---|---|---|
| HTTP Request | net/http | Request | true | false |
| Command Line Args | os | Args | false | true |
| Environment Variables | os | Getenv | false | true |
| File Content | bufio | Reader | true | false |
This repository includes a reusable Copilot skill and prompt for creating new gosec rules from an issue description.
.github/skills/gosec-new-rule/SKILL.md.github/prompts/create-gosec-rule.prompt.md/prompt (recommended)/prompt and select Create Gosec Rule.Summary, repro steps, versions, environment, expected, actual).1.25 and 1.26Confirmed. Proceed with implementation.)./prompt)Send this in Copilot Chat:
Use the skill "Create New Gosec Rule" from .github/skills/gosec-new-rule/SKILL.md.
Then paste the same issue template fields and confirm after the proposal step.
/prompt does not list the prompt.github/prompts/create-gosec-rule.prompt.md.This repository also includes a Copilot skill and prompt for fixing bugs described in GitHub issues.
.github/skills/gosec-fix-issue/SKILL.md.github/prompts/fix-gosec-bug-from-issue.prompt.md/prompt (recommended)/prompt and select Fix Gosec Bug From Issue.GitHub issue URL field (other fields are optional but useful).master (or clear blocker)Confirmed. Proceed with fix.)./prompt)Send this in Copilot Chat:
Use the skill "Fix Gosec Bug From Issue" from .github/skills/gosec-fix-issue/SKILL.md.
Then provide the GitHub issue URL and confirm after the analysis and plan step.
After confirmation, the workflow should:
testutils/ code samples when appropriate for reproducing/validating the issuegolangci-lint, and a gosec CLI run against a sampleThis repository includes a Copilot skill and prompt to update supported Go versions to the latest patch versions of the two newest major Go series.
.github/skills/gosec-update-go-versions/SKILL.md.github/prompts/update-supported-go-versions.prompt.md/prompt (recommended)/prompt and select Update Supported Go Versions.https://go.dev/doc/devel/release/prompt)Send this in Copilot Chat:
Use the skill "Update Supported Go Versions" from .github/skills/gosec-update-go-versions/SKILL.md.
The result should include:
previous_patch, latest_patch, previous_minor, latest_minor)Use these tools while building or debugging rules:
ssadump:ssadump -build F main.go
gosecutil:gosecutil -tool ast main.go
Valid -tool values: ast, callobj, uses, types, defs, comments, imports.
Install schema-generate:
go install github.com/a-h/generate/cmd/schema-generate@latest
Generate types:
schema-generate -i sarif-schema-2.1.0.json -o path/to/types.go
Most MarshalJSON/UnmarshalJSON helpers can be removed after generation, except PropertyBag where inlined additional properties are useful.
CI includes a taint benchmark guard based on BenchmarkTaintPackageAnalyzers_SharedCache.
.github/benchmarks/taint_benchmark_baseline.envtools/check_taint_benchmark.shRun locally:
bash tools/check_taint_benchmark.sh
Update baseline after intentional changes:
BENCH_COUNT=10 bash tools/check_taint_benchmark.sh --update-baseline
If you update the baseline, commit both the benchmark-related code and the baseline file.
The TLS rule data is generated from Mozilla recommendations.
From the repository root:
go generate ./...
If go generate fails with exec: "tlsconfig": executable file not found in $PATH, install the local generator and add $(go env GOPATH)/bin to PATH:
export PATH="$(go env GOPATH)/bin:$PATH"
go install ./cmd/tlsconfig
go generate ./...
This updates rules/tls_config.go.
If you need to install the generator binary outside this repository:
go install github.com/securego/gosec/v2/cmd/tlsconfig@latest
Tag and push:
git tag v1.0.0 -m "Release version v1.0.0"
git push origin v1.0.0
The release workflow builds binaries and Docker images, then signs artifacts.
Verify signatures:
cosign verify --key cosign.pub ghcr.io/securego/gosec:<TAG>
cosign verify-blob --key cosign.pub --signature gosec_<VERSION>_darwin_amd64.tar.gz.sig gosec_<VERSION>_darwin_amd64.tar.gz
Build locally:
make image
Run against a local project:
docker run --rm -it -w /<PROJECT>/ -v <YOUR_PROJECT_PATH>/<PROJECT>:/<PROJECT> ghcr.io/securego/gosec:latest /<PROJECT>/...
Set -w so module dependencies resolve from the mounted project root.