Skip to content
Merged
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
16 changes: 12 additions & 4 deletions sast-engine/cmd/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,18 @@ Examples:
}

// Build code graph (AST)
logger.Progress("Building code graph from %s...", projectPath)
logger.StartProgress("Building code graph", -1)
codeGraph := graph.Initialize(projectPath)
logger.FinishProgress()
if len(codeGraph.Nodes) == 0 {
return fmt.Errorf("no source files found in project")
}
logger.Statistic("Code graph built: %d nodes", len(codeGraph.Nodes))

// Build module registry
logger.Progress("Building module registry...")
logger.StartProgress("Building module registry", -1)
moduleRegistry, err := registry.BuildModuleRegistry(projectPath, skipTests)
logger.FinishProgress()
if err != nil {
logger.Warning("failed to build module registry: %v", err)
moduleRegistry = core.NewModuleRegistry()
Expand All @@ -98,18 +100,20 @@ Examples:
}

// Build callgraph
logger.Progress("Building callgraph...")
logger.StartProgress("Building callgraph", -1)
cg, err := builder.BuildCallGraph(codeGraph, moduleRegistry, projectPath, logger)
logger.FinishProgress()
if err != nil {
return fmt.Errorf("failed to build callgraph: %w", err)
}
logger.Statistic("Callgraph built: %d functions, %d call sites",
len(cg.Functions), countTotalCallSites(cg))

// Load Python DSL rules
logger.Progress("Loading rules from %s...", rulesPath)
logger.StartProgress("Loading rules", -1)
loader := dsl.NewRuleLoader(rulesPath)
rules, err := loader.LoadRules(logger)
logger.FinishProgress()
if err != nil {
return fmt.Errorf("failed to load rules: %w", err)
}
Expand All @@ -130,13 +134,15 @@ Examples:
var scanErrors []string
hadErrors := false

logger.StartProgress("Executing rules", len(rules))
for _, rule := range rules {
detections, err := loader.ExecuteRule(&rule, cg)
if err != nil {
errMsg := fmt.Sprintf("Error executing rule %s: %v", rule.Rule.ID, err)
logger.Warning("%s", errMsg)
scanErrors = append(scanErrors, errMsg)
hadErrors = true
logger.UpdateProgress(1)
continue
}

Expand All @@ -145,7 +151,9 @@ Examples:
enriched, _ := enricher.EnrichAll(detections, rule)
allEnriched = append(allEnriched, enriched...)
}
logger.UpdateProgress(1)
}
logger.FinishProgress()

logger.Statistic("Scan complete. Found %d vulnerabilities", len(allEnriched))
logger.Progress("Generating %s output...", outputFormat)
Expand Down
16 changes: 12 additions & 4 deletions sast-engine/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ Examples:
loader := dsl.NewRuleLoader(rulesPath)

// Step 1: Build code graph (AST)
logger.Progress("Building code graph from %s...", projectPath)
logger.StartProgress("Building code graph", -1)
codeGraph := graph.Initialize(projectPath)
logger.FinishProgress()
if len(codeGraph.Nodes) == 0 {
return fmt.Errorf("no source files found in project")
}
Expand Down Expand Up @@ -160,8 +161,9 @@ Examples:
}

// Step 2: Build module registry
logger.Progress("Building module registry...")
logger.StartProgress("Building module registry", -1)
moduleRegistry, err := registry.BuildModuleRegistry(projectPath, skipTests)
logger.FinishProgress()
if err != nil {
logger.Warning("failed to build module registry: %v", err)
// Create empty registry as fallback
Expand All @@ -172,17 +174,19 @@ Examples:
}

// Step 3: Build callgraph
logger.Progress("Building callgraph...")
logger.StartProgress("Building callgraph", -1)
cg, err := builder.BuildCallGraph(codeGraph, moduleRegistry, projectPath, logger)
logger.FinishProgress()
if err != nil {
return fmt.Errorf("failed to build callgraph: %w", err)
}
logger.Statistic("Callgraph built: %d functions, %d call sites",
len(cg.Functions), countTotalCallSites(cg))

// Step 4: Load Python DSL rules
logger.Progress("Loading rules from %s...", rulesPath)
logger.StartProgress("Loading rules", -1)
rules, err := loader.LoadRules(logger)
logger.FinishProgress()
if err != nil {
return fmt.Errorf("failed to load rules: %w", err)
}
Expand All @@ -201,19 +205,23 @@ Examples:
// Execute all rules and collect enriched detections
var allEnriched []*dsl.EnrichedDetection
var scanErrors bool
logger.StartProgress("Executing rules", len(rules))
for _, rule := range rules {
detections, err := loader.ExecuteRule(&rule, cg)
if err != nil {
logger.Warning("Error executing rule %s: %v", rule.Rule.ID, err)
scanErrors = true
logger.UpdateProgress(1)
continue
}

if len(detections) > 0 {
enriched, _ := enricher.EnrichAll(detections, rule)
allEnriched = append(allEnriched, enriched...)
}
logger.UpdateProgress(1)
}
logger.FinishProgress()

// Merge container detections with code analysis detections
allEnriched = append(allEnriched, containerDetections...)
Expand Down
7 changes: 5 additions & 2 deletions sast-engine/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ require (
)

require (
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
github.com/owenrumney/go-sarif/v2 v2.3.3
github.com/stretchr/testify v1.10.0
golang.org/x/term v0.39.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/schollz/progressbar/v3 v3.19.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
)
6 changes: 6 additions & 0 deletions sast-engine/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU=
github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posthog/posthog-go v1.6.11 h1:5G8Y3pxnOpc3S4+PK1z1dCmZRuldiWxBsqqvvSfC2+w=
github.com/posthog/posthog-go v1.6.11/go.mod h1:LcC1Nu4AgvV22EndTtrMXTy+7RGVC0MhChSw7Qk5XkY=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
Expand Down
117 changes: 102 additions & 15 deletions sast-engine/output/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,47 @@ import (
"io"
"os"
"time"

"github.com/schollz/progressbar/v3"
)

// Logger provides structured logging with verbosity control.
type Logger struct {
verbosity VerbosityLevel
writer io.Writer
startTime time.Time
timings map[string]time.Duration
isTTY bool
verbosity VerbosityLevel
writer io.Writer
startTime time.Time
timings map[string]time.Duration
isTTY bool
progressBar *progressbar.ProgressBar
showProgress bool
}

// NewLogger creates a logger with the specified verbosity.
// Output goes to stderr to keep stdout clean for results.
func NewLogger(verbosity VerbosityLevel) *Logger {
writer := os.Stderr
isTTY := IsTTY(writer)
return &Logger{
verbosity: verbosity,
writer: writer,
startTime: time.Now(),
timings: make(map[string]time.Duration),
isTTY: IsTTY(writer),
verbosity: verbosity,
writer: writer,
startTime: time.Now(),
timings: make(map[string]time.Duration),
isTTY: isTTY,
showProgress: isTTY,
}
}

// NewLoggerWithWriter creates a logger with custom output writer.
// Primarily used for testing.
func NewLoggerWithWriter(verbosity VerbosityLevel, w io.Writer) *Logger {
isTTY := IsTTY(w)
return &Logger{
verbosity: verbosity,
writer: w,
startTime: time.Now(),
timings: make(map[string]time.Duration),
isTTY: IsTTY(w),
verbosity: verbosity,
writer: w,
startTime: time.Now(),
timings: make(map[string]time.Duration),
isTTY: isTTY,
showProgress: isTTY,
}
}

Expand Down Expand Up @@ -142,3 +150,82 @@ func (l *Logger) IsTTY() bool {
func (l *Logger) GetWriter() io.Writer {
return l.writer
}

// StartProgress creates and displays a progress bar.
// For indeterminate operations (total = -1), shows a spinner.
// For determinate operations (total > 0), shows percentage progress.
func (l *Logger) StartProgress(description string, total int) error {
if !l.showProgress || !l.isTTY {
// In non-TTY mode, just print the description
l.Progress("%s...", description)
return nil
}

// Clear any existing progress bar
if l.progressBar != nil {
_ = l.progressBar.Finish()
}

if total < 0 {
// Indeterminate progress (spinner)
l.progressBar = progressbar.NewOptions(-1,
progressbar.OptionSetDescription(description),
progressbar.OptionSetWriter(l.writer),
progressbar.OptionSetWidth(40),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionSpinnerType(14),
progressbar.OptionOnCompletion(func() {
fmt.Fprintf(l.writer, "\n")
}),
)
} else {
// Determinate progress (percentage bar)
l.progressBar = progressbar.NewOptions(total,
progressbar.OptionSetDescription(description),
progressbar.OptionSetWriter(l.writer),
progressbar.OptionSetWidth(40),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionShowCount(),
progressbar.OptionOnCompletion(func() {
fmt.Fprintf(l.writer, "\n")
}),
progressbar.OptionSetRenderBlankState(true),
)
}

return nil
}

// UpdateProgress increments the progress bar by delta.
func (l *Logger) UpdateProgress(delta int) error {
if !l.showProgress || !l.isTTY || l.progressBar == nil {
return nil
}

return l.progressBar.Add(delta)
}

// FinishProgress completes and clears the progress bar.
func (l *Logger) FinishProgress() error {
if !l.showProgress || !l.isTTY || l.progressBar == nil {
return nil
}

err := l.progressBar.Finish()
l.progressBar = nil
return err
}

// SetProgressDescription updates the progress bar description.
func (l *Logger) SetProgressDescription(description string) {
if !l.showProgress || !l.isTTY || l.progressBar == nil {
return
}

l.progressBar.Describe(description)
}

// IsProgressEnabled returns true if progress bars are enabled.
func (l *Logger) IsProgressEnabled() bool {
return l.showProgress && l.isTTY
}
Loading
Loading