Skip to content
Closed
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 .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/poutine
dist/
.claude/
.smoke-test/
71 changes: 70 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SHELL=/usr/bin/env bash
build:
go build -o poutine .

ci: fmt test lint
ci: fmt test lint smoke-test

test:
go test ./... -cover
Expand All @@ -16,3 +16,72 @@ fmt:

lint:
golangci-lint run

.PHONY: smoke-test
smoke-test: smoke-test-build smoke-test-run
@echo "βœ… All smoke tests passed!"

.PHONY: smoke-test-build
smoke-test-build:
@echo "Setting up smoke test environment..."
@if [ -d ".smoke-test" ]; then rm -rf .smoke-test; fi
@mkdir -p .smoke-test
@cp -r . .smoke-test/poutine-build
@cd .smoke-test/poutine-build && rm -f .poutine.yml
@cd .smoke-test/poutine-build && go build -o ../poutine .
@echo "βœ… Smoke test environment ready"

.PHONY: smoke-test-run
smoke-test-run:
@if [ ! -f ".smoke-test/poutine" ]; then echo "❌ Run 'make smoke-test-build' first"; exit 1; fi
@echo "Running smoke tests..."
@cd .smoke-test && \
echo "Testing CLI help..." && \
./poutine --help > help.txt && \
grep -q "analyze_org" help.txt && \
grep -q "analyze_repo" help.txt && \
grep -q "format.*pretty, json, sarif" help.txt && \
echo "βœ… CLI help test passed" && \
echo "Testing JSON format with messypoutine/gravy-overflow analysis..." && \
timeout 120 ./poutine --token $$(gh auth token) analyze_repo messypoutine/gravy-overflow --format=json --quiet > output.json 2>/dev/null && \
jq -e '.findings | length > 10' output.json > /dev/null && \
jq -e '.rules | length > 5' output.json > /dev/null && \
jq -e '.rules | has("injection")' output.json > /dev/null && \
jq -e '.rules | has("debug_enabled")' output.json > /dev/null && \
jq -e '.rules | has("untrusted_checkout_exec")' output.json > /dev/null && \
jq -e '.findings | map(select(.rule_id == "injection")) | length > 0' output.json > /dev/null && \
jq -e '.findings | map(select(.rule_id == "debug_enabled")) | length > 0' output.json > /dev/null && \
jq -e '.findings | map(select(.rule_id == "untrusted_checkout_exec")) | length > 0' output.json > /dev/null && \
jq -e '.findings[] | select(.purl == "pkg:github/messypoutine/gravy-overflow")' output.json > /dev/null && \
echo "βœ… JSON format test passed with expected gravy-overflow findings" && \
echo "Testing pretty format..." && \
timeout 120 ./poutine --token $$(gh auth token) analyze_repo messypoutine/gravy-overflow --format=pretty --quiet > pretty.txt 2>/dev/null && \
grep -q "Rule: CI Runner Debug Enabled" pretty.txt && \
grep -q "Rule: Injection with Arbitrary External Contributor Input" pretty.txt && \
grep -q "Rule: Arbitrary Code Execution from Untrusted Code Changes" pretty.txt && \
grep -q "messypoutine/gravy-overflow" pretty.txt && \
grep -q "ACTIONS_RUNNER_DEBUG" pretty.txt && \
grep -q "github.event.comment.body" pretty.txt && \
grep -q "Summary of findings:" pretty.txt && \
echo "βœ… Pretty format test passed with expected content" && \
echo "Testing SARIF format..." && \
timeout 120 ./poutine --token $$(gh auth token) analyze_repo messypoutine/gravy-overflow --format=sarif --quiet > output.sarif 2>/dev/null && \
jq -e '.["$$schema"]' output.sarif > /dev/null && \
jq -e '.version == "2.1.0"' output.sarif > /dev/null && \
jq -e '.runs | length == 1' output.sarif > /dev/null && \
jq -e '.runs[0].results | length > 10' output.sarif > /dev/null && \
echo "βœ… SARIF format test passed with expected structure" && \
echo "Testing Unicode table rendering with tablewriter v1.0.9..." && \
grep -c "β”Œ" pretty.txt | grep -q "[1-9]" && \
grep -c "β”œ" pretty.txt | grep -q "[1-9]" && \
grep -c "β””" pretty.txt | grep -q "[1-9]" && \
grep -c "β”‚" pretty.txt | grep -q "[1-9]" && \
grep -q "debug_enabled.*CI Runner Debug Enabled.*[1-9].*Failed" pretty.txt && \
grep -q "injection.*Injection with Arbitrary.*[1-9].*Failed" pretty.txt && \
grep -q "untrusted_checkout_exec.*Arbitrary Code Execution.*[1-9].*Failed" pretty.txt && \
echo "βœ… Table rendering test passed with expected summary data"

.PHONY: smoke-test-clean
smoke-test-clean:
@rm -rf .smoke-test
@echo "βœ… Smoke test environment cleaned"
40 changes: 30 additions & 10 deletions formatters/pretty/pretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

"github.com/boostsecurityio/poutine/models"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
)

type Format struct {
Expand Down Expand Up @@ -77,9 +78,16 @@

func (f *Format) printFindingsPerWorkflow(out io.Writer, results map[string]map[string]bool, pathAssociations map[string][]*models.RepoInfo) error {
// Skip rules with no findings.
table := tablewriter.NewWriter(out)
table.SetAutoMergeCells(true)
table.SetHeader([]string{"Workflow sha", "Rule", "Location", "URL"})
table := tablewriter.NewTable(out,
tablewriter.WithConfig(tablewriter.Config{
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
MergeMode: tw.MergeHierarchical,
},
},
}),
)
table.Header("Workflow sha", "Rule", "Location", "URL")

for blobsha, repoInfos := range pathAssociations {
findings := results[blobsha]
Expand Down Expand Up @@ -132,7 +140,7 @@
}
}

table.AppendBulk(blobshaTable)
table.Bulk(blobshaTable)

Check failure on line 143 in formatters/pretty/pretty.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `table.Bulk` is not checked (errcheck)
table.Append([]string{"", "", "", ""})
}

Expand All @@ -155,9 +163,16 @@
continue
}

table := tablewriter.NewWriter(out)
table.SetAutoMergeCells(true)
table.SetHeader([]string{"Repository", "Details", "URL"})
table := tablewriter.NewTable(out,
tablewriter.WithConfig(tablewriter.Config{
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
MergeMode: tw.MergeHierarchical,
},
},
}),
)
table.Header("Repository", "Details", "URL")

fmt.Fprintf(out, "Rule: %s\n", rules[ruleId].Title)
fmt.Fprintf(out, "Severity: %s\n", rules[ruleId].Level)
Expand Down Expand Up @@ -211,9 +226,14 @@
}

func printSummaryTable(out io.Writer, failures map[string]int, rules map[string]results.Rule) {
table := tablewriter.NewWriter(out)
table.SetHeader([]string{"Rule ID", "Rule Name", "Failures", "Status"})
table.SetColWidth(80)
table := tablewriter.NewTable(out,
tablewriter.WithConfig(tablewriter.Config{
Widths: tw.CellWidth{
Global: 80,
},
}),
)
table.Header("Rule ID", "Rule Name", "Failures", "Status")

sortedRuleIDs := make([]string, 0, len(rules))
for ruleID := range rules {
Expand Down
Loading
Loading