169 lines
3.5 KiB
Go
169 lines
3.5 KiB
Go
// ©AngelaMos | 2026
|
|
// sarif.go
|
|
|
|
package reporter
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
|
|
"github.com/CarterPerez-dev/portia/pkg/types"
|
|
)
|
|
|
|
type SARIF struct{}
|
|
|
|
type sarifLog struct {
|
|
Version string `json:"version"`
|
|
Schema string `json:"$schema"`
|
|
Runs []sarifRun `json:"runs"`
|
|
}
|
|
|
|
type sarifRun struct {
|
|
Tool sarifTool `json:"tool"`
|
|
Results []sarifResult `json:"results"`
|
|
}
|
|
|
|
type sarifTool struct {
|
|
Driver sarifDriver `json:"driver"`
|
|
}
|
|
|
|
type sarifDriver struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Rules []sarifRule `json:"rules,omitempty"`
|
|
}
|
|
|
|
type sarifRule struct {
|
|
ID string `json:"id"`
|
|
ShortDescription sarifMessage `json:"shortDescription"`
|
|
DefaultConfig sarifRuleConfig `json:"defaultConfiguration"`
|
|
}
|
|
|
|
type sarifRuleConfig struct {
|
|
Level string `json:"level"`
|
|
}
|
|
|
|
type sarifMessage struct {
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type sarifResult struct {
|
|
RuleID string `json:"ruleId"`
|
|
Level string `json:"level"`
|
|
Message sarifMessage `json:"message"`
|
|
Locations []sarifLocation `json:"locations"`
|
|
Properties map[string]any `json:"properties,omitempty"`
|
|
}
|
|
|
|
type sarifLocation struct {
|
|
PhysicalLocation sarifPhysicalLocation `json:"physicalLocation"`
|
|
}
|
|
|
|
type sarifPhysicalLocation struct {
|
|
ArtifactLocation sarifArtifactLocation `json:"artifactLocation"`
|
|
Region sarifRegion `json:"region"`
|
|
}
|
|
|
|
type sarifArtifactLocation struct {
|
|
URI string `json:"uri"`
|
|
}
|
|
|
|
type sarifRegion struct {
|
|
StartLine int `json:"startLine"`
|
|
}
|
|
|
|
func (s *SARIF) Report(
|
|
w io.Writer, result *types.ScanResult,
|
|
) error {
|
|
ruleMap := make(map[string]bool)
|
|
var sarifRules []sarifRule
|
|
|
|
for _, f := range result.Findings {
|
|
if ruleMap[f.RuleID] {
|
|
continue
|
|
}
|
|
ruleMap[f.RuleID] = true
|
|
sarifRules = append(sarifRules, sarifRule{
|
|
ID: f.RuleID,
|
|
ShortDescription: sarifMessage{Text: f.Description},
|
|
DefaultConfig: sarifRuleConfig{
|
|
Level: severityToSARIF(f.Severity),
|
|
},
|
|
})
|
|
}
|
|
|
|
var results []sarifResult
|
|
for _, f := range result.Findings {
|
|
r := sarifResult{
|
|
RuleID: f.RuleID,
|
|
Level: severityToSARIF(f.Severity),
|
|
Message: sarifMessage{Text: f.Description},
|
|
Locations: []sarifLocation{
|
|
{
|
|
PhysicalLocation: sarifPhysicalLocation{
|
|
ArtifactLocation: sarifArtifactLocation{
|
|
URI: f.FilePath,
|
|
},
|
|
Region: sarifRegion{
|
|
StartLine: f.LineNumber,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
props := make(map[string]any)
|
|
if f.CommitSHA != "" {
|
|
props["commit"] = f.CommitSHA
|
|
}
|
|
if f.Entropy > 0 {
|
|
props["entropy"] = f.Entropy
|
|
}
|
|
if f.HIBPStatus == types.HIBPBreached {
|
|
props["hibp_breached"] = true
|
|
props["breach_count"] = f.BreachCount
|
|
}
|
|
if len(props) > 0 {
|
|
r.Properties = props
|
|
}
|
|
|
|
results = append(results, r)
|
|
}
|
|
|
|
log := sarifLog{
|
|
Version: "2.1.0",
|
|
Schema: "https://raw.githubusercontent.com/" +
|
|
"oasis-tcs/sarif-spec/main/sarif-2.1/" +
|
|
"schema/sarif-schema-2.1.0.json",
|
|
Runs: []sarifRun{
|
|
{
|
|
Tool: sarifTool{
|
|
Driver: sarifDriver{
|
|
Name: "portia",
|
|
Version: "1.0.0",
|
|
Rules: sarifRules,
|
|
},
|
|
},
|
|
Results: results,
|
|
},
|
|
},
|
|
}
|
|
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(log)
|
|
}
|
|
|
|
func severityToSARIF(sev types.Severity) string {
|
|
switch sev {
|
|
case types.SeverityCritical, types.SeverityHigh:
|
|
return "error"
|
|
case types.SeverityMedium:
|
|
return "warning"
|
|
case types.SeverityLow:
|
|
return "note"
|
|
default:
|
|
return "note"
|
|
}
|
|
}
|