Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ Inspects source code for security problems by scanning the Go AST and SSA code r

<img src="https://securego.io/img/gosec.png" width="320">

## Features

- **Pattern-based rules** for detecting common security issues in Go code
- **SSA-based analyzers** for type conversions, slice bounds, and crypto issues
- **Taint analysis** for tracking data flow from user input to dangerous functions (SQL injection, command injection, path traversal, SSRF, XSS, log injection)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the developer section, it would be great to add some details which describes how to create a taint rule.


## License

Licensed under the Apache License, Version 2.0 (the "License").
Expand Down Expand Up @@ -216,6 +222,12 @@ directory you can supply `./...` as the input argument.
- G507: Import blocklist: golang.org/x/crypto/ripemd160
- G601: Implicit memory aliasing of items from a range statement (only for Go 1.21 or lower)
- G602: Slice access out of bounds
- G701: SQL injection via taint analysis
- G702: Command injection via taint analysis
- G703: Path traversal via taint analysis
- G704: SSRF via taint analysis
- G705: XSS via taint analysis
- G706: Log injection via taint analysis

### Retired rules

Expand Down Expand Up @@ -498,6 +510,112 @@ $ gosec -fmt=json -out=results.json -stdout -verbose=text *.go

[CONTRIBUTING.md](https://github.com/securego/gosec/blob/master/CONTRIBUTING.md) contains detailed information about adding new rules to gosec.

### Creating Taint Analysis Rules

gosec supports taint analysis to track data flow from untrusted sources (user input) to dangerous sinks (functions that could cause security vulnerabilities). The taint analysis rules (G701-G706) detect issues like SQL injection, command injection, path traversal, SSRF, XSS, and log injection.

#### Creating a New Taint Rule

To create a new taint analysis rule:

1. **Define the configuration** in `analyzers/taint/configs.go`:

```go
// NewVulnerability returns a configuration for detecting your vulnerability
func NewVulnerability() Config {
return Config{
Sources: []Source{
{Package: "net/http", Name: "Request", Pointer: true},
{Package: "os", Name: "Args"},
},
Sinks: []Sink{
{Package: "dangerous/package", Method: "DangerousFunc"},
{Package: "another/pkg", Receiver: "Type", Method: "Method", Pointer: true},
},
}
}
```

2. **Create the analyzer file** in `analyzers/` (e.g., `analyzers/newvuln.go`):

```go
package analyzers

import (
"golang.org/x/tools/go/analysis"
"github.com/securego/gosec/v2/analyzers/taint"
)

func newNewVulnAnalyzer(id string, description string) *analysis.Analyzer {
config := taint.NewVulnerability()
rule := taint.RuleInfo{
ID: id,
Description: description,
Severity: "HIGH", // or LOW, MEDIUM, CRITICAL
CWE: "CWE-XXX",
}
return taint.NewGosecAnalyzer(&rule, &config)
}
```

3. **Register the analyzer** in `analyzers/analyzerslist.go`:

```go
var defaultAnalyzers = []AnalyzerDefinition{
// ... existing analyzers ...
{"G7XX", "Description of vulnerability", newNewVulnAnalyzer},
}
```

#### Source and Sink Configuration

**Sources** define where tainted (untrusted) data originates:
- `Package`: The import path (e.g., `"net/http"`)
- `Name`: The type or function name (e.g., `"Request"`)
- `Pointer`: Set to `true` if it's a pointer type (e.g., `*http.Request`)

**Sinks** define dangerous functions that should not receive tainted data:
- `Package`: The import path (e.g., `"database/sql"`)
- `Receiver`: The type name for methods (e.g., `"DB"`), or empty for package functions
- `Method`: The function or method name (e.g., `"Query"`)
- `Pointer`: Set to `true` if the receiver is a pointer type

#### Common Taint Sources

| Source Type | Package | Type/Method | Pointer |
|-------------|---------|-------------|---------|
| HTTP Request | `net/http` | `Request` | `true` |
| Query Parameters | `net/http` | `Request.URL.Query()` | - |
| Form Data | `net/http` | `Request.FormValue()` | - |
| Headers | `net/http` | `Request.Header` | - |
| Command Line Args | `os` | `Args` | `false` |
| Environment Variables | `os` | `Getenv` | `false` |
| File Content | `bufio` | `Reader` | `true` |

#### Testing Your Rule

Create tests in `analyzers/taint/` using the `buildSSA` helper:

```go
func TestAnalyzeNewVulnerability(t *testing.T) {
src := `package main
import ("dangerous/package"; "net/http")
func handler(r *http.Request) {
input := r.URL.Query().Get("param")
dangerous.DangerousFunc(input) // Should detect
}`

prog, funcs := buildSSA(t, src)
config := NewVulnerability()
analyzer := New(&config)
results := analyzer.Analyze(prog, funcs)

if len(results) == 0 {
t.Error("expected vulnerability detection")
}
}
```

### Build

You can build the binary with:
Expand Down
6 changes: 6 additions & 0 deletions analyzers/analyzerslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ var defaultAnalyzers = []AnalyzerDefinition{
{"G115", "Type conversion which leads to integer overflow", newConversionOverflowAnalyzer},
{"G602", "Possible slice bounds out of range", newSliceBoundsAnalyzer},
{"G407", "Use of hardcoded IV/nonce for encryption", newHardCodedNonce},
{"G701", "SQL injection via taint analysis", newSQLInjectionAnalyzer},
{"G702", "Command injection via taint analysis", newCommandInjectionAnalyzer},
{"G703", "Path traversal via taint analysis", newPathTraversalAnalyzer},
{"G704", "SSRF via taint analysis", newSSRFAnalyzer},
{"G705", "XSS via taint analysis", newXSSAnalyzer},
{"G706", "Log injection via taint analysis", newLogInjectionAnalyzer},
}

// Generate the list of analyzers to use
Expand Down
136 changes: 136 additions & 0 deletions analyzers/analyzerslist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// (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 (
"testing"
)

// TestTaintAnalyzerConstructors tests that all taint analyzer constructors work.
func TestTaintAnalyzerConstructors(t *testing.T) {
tests := []struct {
name string
constructor AnalyzerBuilder
id string
description string
}{
{
name: "SQLInjection",
constructor: newSQLInjectionAnalyzer,
id: "G701",
description: "SQL injection via taint analysis",
},
{
name: "CommandInjection",
constructor: newCommandInjectionAnalyzer,
id: "G702",
description: "Command injection via taint analysis",
},
{
name: "PathTraversal",
constructor: newPathTraversalAnalyzer,
id: "G703",
description: "Path traversal via taint analysis",
},
{
name: "SSRF",
constructor: newSSRFAnalyzer,
id: "G704",
description: "SSRF via taint analysis",
},
{
name: "XSS",
constructor: newXSSAnalyzer,
id: "G705",
description: "XSS via taint analysis",
},
{
name: "LogInjection",
constructor: newLogInjectionAnalyzer,
id: "G706",
description: "Log injection via taint analysis",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
analyzer := tt.constructor(tt.id, tt.description)

if analyzer == nil {
t.Fatal("constructor returned nil")
}

if analyzer.Name != tt.id {
t.Errorf("analyzer Name = %s, want %s", analyzer.Name, tt.id)
}

if analyzer.Run == nil {
t.Error("analyzer Run function is nil")
}

if len(analyzer.Requires) == 0 {
t.Error("analyzer has no requirements")
}
})
}
}

// TestDefaultAnalyzersIncludeTaint tests that default analyzers include taint rules.
func TestDefaultAnalyzersIncludeTaint(t *testing.T) {
expectedTaintIDs := []string{"G701", "G702", "G703", "G704", "G705", "G706"}

found := make(map[string]bool)
for _, def := range defaultAnalyzers {
found[def.ID] = true
}

for _, id := range expectedTaintIDs {
if !found[id] {
t.Errorf("default analyzers missing taint rule: %s", id)
}
}
}

// TestGenerateIncludesTaintAnalyzers tests that Generate includes taint analyzers.
func TestGenerateIncludesTaintAnalyzers(t *testing.T) {
analyzerList := Generate(false)

expectedTaintIDs := []string{"G701", "G702", "G703", "G704", "G705", "G706"}

for _, id := range expectedTaintIDs {
if _, ok := analyzerList.Analyzers[id]; !ok {
t.Errorf("generated analyzer list missing taint rule: %s", id)
}
}
}

// TestGenerateExcludeTaintAnalyzers tests that taint analyzers can be excluded.
func TestGenerateExcludeTaintAnalyzers(t *testing.T) {
filter := NewAnalyzerFilter(true, "G701", "G702")
analyzerList := Generate(false, filter)

if _, ok := analyzerList.Analyzers["G701"]; ok {
t.Error("G701 should be excluded but was found")
}

if _, ok := analyzerList.Analyzers["G702"]; ok {
t.Error("G702 should be excluded but was found")
}

// Other taint analyzers should still be present
if _, ok := analyzerList.Analyzers["G703"]; !ok {
t.Error("G703 should be present but was not found")
}
}
34 changes: 34 additions & 0 deletions analyzers/commandinjection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// (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"

"github.com/securego/gosec/v2/analyzers/taint"
)

// newCommandInjectionAnalyzer creates an analyzer for detecting command injection vulnerabilities
// via taint analysis (G702)
func newCommandInjectionAnalyzer(id string, description string) *analysis.Analyzer {
config := taint.CommandInjection()
rule := taint.RuleInfo{
ID: id,
Description: description,
Severity: "CRITICAL",
CWE: "CWE-78",
}
return taint.NewGosecAnalyzer(&rule, &config)
}
34 changes: 34 additions & 0 deletions analyzers/loginjection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// (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"

"github.com/securego/gosec/v2/analyzers/taint"
)

// newLogInjectionAnalyzer creates an analyzer for detecting log injection vulnerabilities
// via taint analysis (G706)
func newLogInjectionAnalyzer(id string, description string) *analysis.Analyzer {
config := taint.LogInjection()
rule := taint.RuleInfo{
ID: id,
Description: description,
Severity: "LOW",
CWE: "CWE-117",
}
return taint.NewGosecAnalyzer(&rule, &config)
}
34 changes: 34 additions & 0 deletions analyzers/pathtraversal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// (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"

"github.com/securego/gosec/v2/analyzers/taint"
)

// newPathTraversalAnalyzer creates an analyzer for detecting path traversal vulnerabilities
// via taint analysis (G703)
func newPathTraversalAnalyzer(id string, description string) *analysis.Analyzer {
config := taint.PathTraversal()
rule := taint.RuleInfo{
ID: id,
Description: description,
Severity: "HIGH",
CWE: "CWE-22",
}
return taint.NewGosecAnalyzer(&rule, &config)
}
Loading
Loading