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
2 changes: 2 additions & 0 deletions internal/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
generateVEX bool
sbomOutput string
vexOutput string
summaryTop bool
)

var scanCmd = &cobra.Command{
Expand All @@ -34,6 +35,7 @@ func init() {
scanCmd.PersistentFlags().BoolVar(&generateVEX, "vex", false, "Generate Vulnerability Exploitability eXchange (VEX) document")
scanCmd.PersistentFlags().StringVar(&sbomOutput, "sbom-output", "", "Output file path for SBOM (default: .armis/<artifact>-sbom.json)")
scanCmd.PersistentFlags().StringVar(&vexOutput, "vex-output", "", "Output file path for VEX (default: .armis/<artifact>-vex.json)")
scanCmd.PersistentFlags().BoolVar(&summaryTop, "summary-top", false, "Display summary at the top of output (before findings)")
if rootCmd != nil {
rootCmd.AddCommand(scanCmd)
}
Expand Down
7 changes: 4 additions & 3 deletions internal/cmd/scan_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ var scanImageCmd = &cobra.Command{
}

opts := output.FormatOptions{
GroupBy: groupBy,
RepoPath: "",
Debug: debug,
GroupBy: groupBy,
RepoPath: "",
Debug: debug,
SummaryTop: summaryTop,
}

if err := formatter.FormatWithOptions(result, os.Stdout, opts); err != nil {
Expand Down
7 changes: 4 additions & 3 deletions internal/cmd/scan_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ var scanRepoCmd = &cobra.Command{
}

opts := output.FormatOptions{
GroupBy: groupBy,
RepoPath: repoPath,
Debug: debug,
GroupBy: groupBy,
RepoPath: repoPath,
Debug: debug,
SummaryTop: summaryTop,
}

if err := formatter.FormatWithOptions(result, os.Stdout, opts); err != nil {
Expand Down
76 changes: 62 additions & 14 deletions internal/output/human.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import (
const (
groupBySeverity = "severity"
noCWELabel = "No CWE"

// Resource limits for snippet loading to prevent memory exhaustion (CWE-770)
maxLineLength = 10 * 1024 // 10KB max per line
maxSnippetSize = 100 * 1024 // 100KB max total snippet size
)

type errWriter struct {
Expand Down Expand Up @@ -110,12 +114,26 @@ func (f *HumanFormatter) FormatWithOptions(result *model.ScanResult, w io.Writer
ew.write("Status: %s\n", result.Status)
ew.write("\n")

// 3. Brief status line for immediate orientation
if err := renderBriefStatus(w, result); err != nil {
return err
// 3. Brief status line for immediate orientation (skip if full summary at top)
if !opts.SummaryTop {
if err := renderBriefStatus(w, result); err != nil {
return err
}
}

// 4. Summary at top if requested
if opts.SummaryTop {
ew.write("\n")
ew.write("───────────────────────────────────────────────────────────────\n")
ew.write(" SUMMARY\n")
ew.write("───────────────────────────────────────────────────────────────\n")
ew.write("\n")
if err := renderSummaryDashboard(w, result); err != nil {
return err
}
}

// 4. Findings section
// 5. Findings section
if len(result.Findings) > 0 {
ew.write("\n")
ew.write("───────────────────────────────────────────────────────────────\n")
Expand All @@ -133,15 +151,17 @@ func (f *HumanFormatter) FormatWithOptions(result *model.ScanResult, w io.Writer
}
}

// 6. Full detailed summary dashboard at the end
ew.write("───────────────────────────────────────────────────────────────\n")
ew.write(" SUMMARY\n")
ew.write("───────────────────────────────────────────────────────────────\n")
ew.write("\n")
if err := renderSummaryDashboard(w, result); err != nil {
return err
// 6. Full detailed summary dashboard at the end (skip if already shown at top)
if !opts.SummaryTop {
ew.write("───────────────────────────────────────────────────────────────\n")
ew.write(" SUMMARY\n")
ew.write("───────────────────────────────────────────────────────────────\n")
ew.write("\n")
if err := renderSummaryDashboard(w, result); err != nil {
return err
}
ew.write("\n")
}
ew.write("\n")

ew.write("═══════════════════════════════════════════════════════════════\n")
ew.write("\n")
Expand Down Expand Up @@ -305,7 +325,11 @@ func loadSnippetFromFile(repoPath string, finding model.Finding) (snippet string
contextEnd := end + 4

scanner := bufio.NewScanner(f)
// Set a bounded buffer to prevent memory exhaustion from extremely long lines
scanner.Buffer(make([]byte, 4096), maxLineLength)

var buf []string
var totalSize int
lineNum := 0
for scanner.Scan() {
lineNum++
Expand All @@ -315,10 +339,34 @@ func loadSnippetFromFile(repoPath string, finding model.Finding) (snippet string
if lineNum > contextEnd {
break
}
buf = append(buf, scanner.Text())
line := scanner.Text()

// Truncate line if it exceeds max length (shouldn't happen with bounded scanner,
// but provides defense in depth)
if len(line) > maxLineLength {
line = line[:maxLineLength] + "... (truncated)"
}

// Check total size limit to prevent memory exhaustion
totalSize += len(line) + 1 // +1 for newline
if totalSize > maxSnippetSize {
buf = append(buf, "... (snippet truncated due to size)")
break
}

buf = append(buf, line)
}
if err := scanner.Err(); err != nil {
return "", 0, fmt.Errorf("scan file: %w", err)
// Handle bufio.ErrTooLong gracefully - the scanner hit its buffer limit
if err == bufio.ErrTooLong {
if len(buf) > 0 {
buf = append(buf, "... (line too long, truncated)")
} else {
return "", 0, fmt.Errorf("file contains lines exceeding size limit")
}
} else {
return "", 0, fmt.Errorf("scan file: %w", err)
}
}
if len(buf) == 0 {
return "", 0, fmt.Errorf("no lines read")
Expand Down
7 changes: 4 additions & 3 deletions internal/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ var (

// FormatOptions contains options for formatting scan results.
type FormatOptions struct {
GroupBy string
RepoPath string
Debug bool
GroupBy string
RepoPath string
Debug bool
SummaryTop bool
}

// Formatter is the interface for formatting scan results in different output formats.
Expand Down
Loading