mirror of
https://github.com/securego/gosec.git
synced 2024-12-25 12:05:52 +00:00
Adding some inline documentation for godoc
This commit is contained in:
parent
37205e9afa
commit
223cded656
5 changed files with 53 additions and 12 deletions
|
@ -12,6 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package core holds the central scanning logic used by GAS
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -26,6 +27,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 withe the encoutered AST node.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
FileSet *token.FileSet
|
FileSet *token.FileSet
|
||||||
Comments ast.CommentMap
|
Comments ast.CommentMap
|
||||||
|
@ -35,12 +39,17 @@ type Context struct {
|
||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Rule interface used by all rules supported by GAS.
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
Match(ast.Node, *Context) (*Issue, error)
|
Match(ast.Node, *Context) (*Issue, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A RuleSet maps lists of rules to the type of AST node they should be run on.
|
||||||
|
// The anaylzer will only invoke rules contained in the list associated with the
|
||||||
|
// type of AST node it is currently visiting.
|
||||||
type RuleSet map[reflect.Type][]Rule
|
type RuleSet map[reflect.Type][]Rule
|
||||||
|
|
||||||
|
// Metrics used when reporting information about a scanning run.
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
NumFiles int `json:"files"`
|
NumFiles int `json:"files"`
|
||||||
NumLines int `json:"lines"`
|
NumLines int `json:"lines"`
|
||||||
|
@ -48,6 +57,8 @@ type Metrics struct {
|
||||||
NumFound int `json:"found"`
|
NumFound int `json:"found"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Analyzer object is the main object of GAS. It has methods traverse an AST
|
||||||
|
// and invoke the correct checking rules as on each node as required.
|
||||||
type Analyzer struct {
|
type Analyzer struct {
|
||||||
ignoreNosec bool
|
ignoreNosec bool
|
||||||
ruleset RuleSet
|
ruleset RuleSet
|
||||||
|
@ -57,6 +68,7 @@ type Analyzer struct {
|
||||||
Stats Metrics `json:"metrics"`
|
Stats Metrics `json:"metrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewAnalyzer buildas a new anaylzer.
|
||||||
func NewAnalyzer(conf map[string]interface{}, logger *log.Logger) Analyzer {
|
func NewAnalyzer(conf map[string]interface{}, logger *log.Logger) Analyzer {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.New(os.Stdout, "[gas]", 0)
|
logger = log.New(os.Stdout, "[gas]", 0)
|
||||||
|
@ -104,6 +116,8 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddRule adds a rule into a rule set list mapped to the given AST node's type.
|
||||||
|
// The node is only needed for its type and is not otherwise used.
|
||||||
func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
|
func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
|
||||||
t := reflect.TypeOf(n)
|
t := reflect.TypeOf(n)
|
||||||
if val, ok := gas.ruleset[t]; ok {
|
if val, ok := gas.ruleset[t]; ok {
|
||||||
|
@ -113,6 +127,8 @@ func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process reads in a source file, convert it to an AST and traverse it.
|
||||||
|
// Rule methods added with AddRule will be invoked as necessary.
|
||||||
func (gas *Analyzer) Process(filename string) error {
|
func (gas *Analyzer) Process(filename string) error {
|
||||||
err := gas.process(filename, nil)
|
err := gas.process(filename, nil)
|
||||||
fun := func(f *token.File) bool {
|
fun := func(f *token.File) bool {
|
||||||
|
@ -123,6 +139,9 @@ func (gas *Analyzer) Process(filename string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessSource will convert a source code string into an AST and traverse it.
|
||||||
|
// Rule methods added with AddRule will be invoked as necessary. The string is
|
||||||
|
// identified by the filename given but no file IO will be done.
|
||||||
func (gas *Analyzer) ProcessSource(filename string, source string) error {
|
func (gas *Analyzer) ProcessSource(filename string, source string) error {
|
||||||
err := gas.process(filename, source)
|
err := gas.process(filename, source)
|
||||||
fun := func(f *token.File) bool {
|
fun := func(f *token.File) bool {
|
||||||
|
@ -133,7 +152,8 @@ func (gas *Analyzer) ProcessSource(filename string, source string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gas *Analyzer) Ignore(n ast.Node) bool {
|
// ignore a node (and sub-tree) if it is tagged with a "nosec" comment
|
||||||
|
func (gas *Analyzer) ignore(n ast.Node) bool {
|
||||||
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
|
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
if strings.Contains(group.Text(), "nosec") {
|
if strings.Contains(group.Text(), "nosec") {
|
||||||
|
@ -145,8 +165,10 @@ func (gas *Analyzer) Ignore(n ast.Node) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visit runs the GAS visitor logic over an AST created by parsing go code.
|
||||||
|
// Rule methods added with AddRule will be invoked as necessary.
|
||||||
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
||||||
if !gas.Ignore(n) {
|
if !gas.ignore(n) {
|
||||||
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
|
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
|
||||||
for _, rule := range val {
|
for _, rule := range val {
|
||||||
ret, err := rule.Match(n, &gas.context)
|
ret, err := rule.Match(n, &gas.context)
|
||||||
|
|
|
@ -37,6 +37,7 @@ func selectName(n ast.Node, s reflect.Type) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchCall will match an ast.CallNode if its method name obays the given regex.
|
||||||
func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
||||||
t := reflect.TypeOf(&ast.CallExpr{})
|
t := reflect.TypeOf(&ast.CallExpr{})
|
||||||
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
||||||
|
@ -45,6 +46,7 @@ func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatcMatchCompLit hCall will match an ast.CompositeLit if its string value obays the given regex.
|
||||||
func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
|
func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
|
||||||
t := reflect.TypeOf(&ast.CompositeLit{})
|
t := reflect.TypeOf(&ast.CompositeLit{})
|
||||||
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
||||||
|
@ -53,6 +55,7 @@ func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInt will read and return an integer value from an ast.BasicLit
|
||||||
func GetInt(n ast.Node) (int64, error) {
|
func GetInt(n ast.Node) (int64, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
|
||||||
return strconv.ParseInt(node.Value, 0, 64)
|
return strconv.ParseInt(node.Value, 0, 64)
|
||||||
|
@ -60,6 +63,7 @@ func GetInt(n ast.Node) (int64, error) {
|
||||||
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInt will read and return a float value from an ast.BasicLit
|
||||||
func GetFloat(n ast.Node) (float64, error) {
|
func GetFloat(n ast.Node) (float64, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
|
||||||
return strconv.ParseFloat(node.Value, 64)
|
return strconv.ParseFloat(node.Value, 64)
|
||||||
|
@ -67,6 +71,7 @@ func GetFloat(n ast.Node) (float64, error) {
|
||||||
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
|
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInt will read and return a char value from an ast.BasicLit
|
||||||
func GetChar(n ast.Node) (byte, error) {
|
func GetChar(n ast.Node) (byte, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
|
||||||
return node.Value[0], nil
|
return node.Value[0], nil
|
||||||
|
@ -74,6 +79,7 @@ func GetChar(n ast.Node) (byte, error) {
|
||||||
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInt will read and return a string value from an ast.BasicLit
|
||||||
func GetString(n ast.Node) (string, error) {
|
func GetString(n ast.Node) (string, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
|
||||||
return strconv.Unquote(node.Value)
|
return strconv.Unquote(node.Value)
|
||||||
|
|
|
@ -20,33 +20,39 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Score type used by severity and confidence values
|
||||||
type Score int
|
type Score int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Low Score = iota
|
Low Score = iota // Low value
|
||||||
Medium
|
Medium // Medium value
|
||||||
High
|
High // High value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An Issue is returnd by a GAS rule if it discovers an issue with the scanned code.
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
Severity Score `json:"severity"`
|
Severity Score `json:"severity"` // issue severity (how problematic it is)
|
||||||
Confidence Score `json:"confidence"`
|
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it)
|
||||||
What string `json:"details"`
|
What string `json:"details"` // Human readable explanation
|
||||||
File string `json:"file"`
|
File string `json:"file"` // File name we found it in
|
||||||
Code string `json:"code"`
|
Code string `json:"code"` // Impacted code line
|
||||||
Line int `json:"line"`
|
Line int `json:"line"` // Line number in file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MetaData is embedded in all GAS rules. The Severity, Confidence and What message
|
||||||
|
// will be passed tbhrough to reported issues.
|
||||||
type MetaData struct {
|
type MetaData struct {
|
||||||
Severity Score
|
Severity Score
|
||||||
Confidence Score
|
Confidence Score
|
||||||
What string
|
What string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON is used convert a Score object into a JSON representation
|
||||||
func (c Score) MarshalJSON() ([]byte, error) {
|
func (c Score) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(c.String())
|
return json.Marshal(c.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String converts a Score into a string
|
||||||
func (c Score) String() string {
|
func (c Score) String() string {
|
||||||
switch c {
|
switch c {
|
||||||
case High:
|
case High:
|
||||||
|
@ -74,6 +80,7 @@ func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, err
|
||||||
return string(buf), nil
|
return string(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewIssue creates a new Issue
|
||||||
func NewIssue(ctx *Context, node ast.Node, desc string, severity Score, confidence Score) *Issue {
|
func NewIssue(ctx *Context, node ast.Node, desc string, severity Score, confidence Score) *Issue {
|
||||||
var code string
|
var code string
|
||||||
fobj := ctx.FileSet.File(node.Pos())
|
fobj := ctx.FileSet.File(node.Pos())
|
||||||
|
|
|
@ -53,6 +53,9 @@ func resolveCallExpr(n *ast.CallExpr, c *Context) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryResolve will attempt, given a subtree starting at some ATS node, to resolve
|
||||||
|
// all values contained within to a known constant. It is used to check for any
|
||||||
|
// unkown values in compound expressions.
|
||||||
func TryResolve(n ast.Node, c *Context) bool {
|
func TryResolve(n ast.Node, c *Context) bool {
|
||||||
switch node := n.(type) {
|
switch node := n.(type) {
|
||||||
case *ast.BasicLit:
|
case *ast.BasicLit:
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A selector function. This is like a visitor, but has a richer interface. It
|
// SelectFunc is like an AST visitor, but has a richer interface. It
|
||||||
// is called with the current ast.Node being visitied and that nodes depth in
|
// is called with the current ast.Node being visitied and that nodes depth in
|
||||||
// the tree. The function can return true to continue traversing the tree, or
|
// the tree. The function can return true to continue traversing the tree, or
|
||||||
// false to end traversal here.
|
// false to end traversal here.
|
||||||
|
@ -379,6 +379,9 @@ func Select(s Selector, n ast.Node, bits ...reflect.Type) {
|
||||||
depthWalk(n, 0, fun)
|
depthWalk(n, 0, fun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimpleSelect will try to match a path through a sub-tree starting at a given AST node.
|
||||||
|
// The type of each node in the path at a given depth must match its entry in list of
|
||||||
|
// node types given.
|
||||||
func SimpleSelect(n ast.Node, bits ...reflect.Type) ast.Node {
|
func SimpleSelect(n ast.Node, bits ...reflect.Type) ast.Node {
|
||||||
var found ast.Node
|
var found ast.Node
|
||||||
fun := func(n ast.Node, d int) bool {
|
fun := func(n ast.Node, d int) bool {
|
||||||
|
|
Loading…
Reference in a new issue