internal/checks/sarif.go
Rama main · réplica de lectura del repositorio original.
package checks
import (
"crypto/sha256"
"encoding/hex"
"strings"
"github.com/sophotechlabs/spinoza/internal/api"
"github.com/sophotechlabs/spinoza/internal/version"
)
const (
sarifSchemaURI = "https://json.schemastore.org/sarif-2.1.0.json"
sarifVersion = "2.1.0"
sarifToolName = "spinoza"
sarifToolURI = "https://spinoza.tech"
sarifPrintKey = "spinozaCheckIdentity/v1"
sarifMuteKind = "external"
sarifNoObject = "an object this report did not carry"
sarifIdentitySep = "\x00"
)
type sarifLevels struct {
level string
problem string
}
var sarifBySeverity = map[string]sarifLevels{
severityHigh: {level: "error", problem: "error"},
severityMedium: {level: "warning", problem: "warning"},
severityLow: {level: "note", problem: "recommendation"},
}
var sarifUnknownSeverity = sarifLevels{level: "none", problem: "recommendation"}
type SARIFLog struct {
Schema string `json:"$schema"`
Version string `json:"version"`
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"`
InformationURI string `json:"informationUri"`
Rules []SARIFRule `json:"rules"`
}
type SARIFRule struct {
ID string `json:"id"`
Name string `json:"name"`
ShortDescription SARIFText `json:"shortDescription"`
FullDescription SARIFText `json:"fullDescription"`
Help SARIFText `json:"help"`
DefaultConfiguration SARIFConfiguration `json:"defaultConfiguration"`
Properties SARIFRuleProperties `json:"properties"`
}
type SARIFConfiguration struct {
Level string `json:"level"`
}
type SARIFRuleProperties struct {
Tags []string `json:"tags"`
Problem SARIFProblem `json:"problem"`
}
type SARIFProblem struct {
Severity string `json:"severity"`
}
type SARIFText struct {
Text string `json:"text"`
}
type SARIFResult struct {
RuleID string `json:"ruleId"`
Level string `json:"level"`
Message SARIFText `json:"message"`
Locations []SARIFLocation `json:"locations"`
PartialFingerprints map[string]string `json:"partialFingerprints"`
Suppressions []SARIFSuppression `json:"suppressions,omitempty"`
}
type SARIFLocation struct {
LogicalLocations []SARIFLogicalLocation `json:"logicalLocations"`
}
type SARIFLogicalLocation struct {
Name string `json:"name"`
FullyQualifiedName string `json:"fullyQualifiedName"`
}
type SARIFSuppression struct {
Kind string `json:"kind"`
Justification string `json:"justification,omitempty"`
}
func SARIF(report api.CheckReport) SARIFLog {
return SARIFLog{
Schema: sarifSchemaURI,
Version: sarifVersion,
Runs: []SARIFRun{{
Tool: SARIFTool{Driver: SARIFDriver{
Name: sarifToolName,
Version: version.String(),
InformationURI: sarifToolURI,
Rules: sarifRules(report),
}},
Results: sarifResults(report),
}},
}
}
func sarifLevelsFor(severity string) sarifLevels {
levels, known := sarifBySeverity[severity]
if !known {
return sarifUnknownSeverity
}
return levels
}
func sarifRules(report api.CheckReport) []SARIFRule {
out := make([]SARIFRule, 0, len(report.Groups))
for _, group := range report.Groups {
levels := sarifLevelsFor(group.Severity)
out = append(out, SARIFRule{
ID: group.ID,
Name: group.ID,
ShortDescription: SARIFText{Text: group.Title},
FullDescription: SARIFText{Text: group.Wrong},
Help: SARIFText{Text: group.Remedy},
DefaultConfiguration: SARIFConfiguration{Level: levels.level},
Properties: SARIFRuleProperties{
Tags: sarifTags(group),
Problem: SARIFProblem{Severity: levels.problem},
},
})
}
return out
}
func sarifTags(group api.CheckGroup) []string {
out := make([]string, 0, len(group.Frameworks)+1)
if group.Category != "" {
out = append(out, group.Category)
}
return append(out, group.Frameworks...)
}
func sarifResults(report api.CheckReport) []SARIFResult {
out := make([]SARIFResult, 0, sarifFindings(report))
for _, group := range report.Groups {
for _, finding := range group.Findings {
out = append(out, sarifResultFor(report, group, finding))
}
}
return out
}
func sarifFindings(report api.CheckReport) int {
total := 0
for _, group := range report.Groups {
total += len(group.Findings)
}
return total
}
func sarifResultFor(report api.CheckReport, group api.CheckGroup, finding api.CheckFinding) SARIFResult {
object := sarifObjectOf(report, finding)
return SARIFResult{
RuleID: group.ID,
Level: sarifLevelsFor(sarifSeverityOf(group, finding)).level,
Message: SARIFText{Text: sarifMessage(group, object, finding)},
Locations: sarifLocations(object),
PartialFingerprints: map[string]string{sarifPrintKey: sarifFingerprint(group.ID, object, finding)},
Suppressions: sarifSuppressions(finding),
}
}
func sarifObjectOf(report api.CheckReport, finding api.CheckFinding) api.CheckObject {
if finding.Ref < 0 || finding.Ref >= len(report.Objects) {
return api.CheckObject{}
}
return report.Objects[finding.Ref]
}
func sarifSeverityOf(group api.CheckGroup, finding api.CheckFinding) string {
if finding.Severity != "" {
return finding.Severity
}
return group.Severity
}
func sarifLocations(object api.CheckObject) []SARIFLocation {
path := sarifPath(object)
if path == "" {
return []SARIFLocation{}
}
return []SARIFLocation{{
LogicalLocations: []SARIFLogicalLocation{{
Name: object.Name,
FullyQualifiedName: path,
}},
}}
}
func sarifPath(object api.CheckObject) string {
parts := make([]string, 0, 3)
if object.Kind != "" {
parts = append(parts, object.Kind)
}
if object.Namespace != "" {
parts = append(parts, object.Namespace)
}
if object.Name != "" {
parts = append(parts, object.Name)
}
return strings.Join(parts, "/")
}
func sarifMessage(group api.CheckGroup, object api.CheckObject, finding api.CheckFinding) string {
subject := sarifSubject(object)
if finding.Container != "" {
subject += " container " + finding.Container
}
return subject + ": " + sarifWrong(group, finding)
}
func sarifSubject(object api.CheckObject) string {
name := object.Name
if object.Namespace != "" && object.Name != "" {
name = object.Namespace + "/" + object.Name
}
if object.Kind == "" && name == "" {
return sarifNoObject
}
if object.Kind == "" {
return name
}
if name == "" {
return object.Kind
}
return object.Kind + " " + name
}
func sarifWrong(group api.CheckGroup, finding api.CheckFinding) string {
if finding.Detail != "" {
return finding.Detail
}
if group.Wrong != "" {
return group.Wrong
}
return group.Title
}
func sarifFingerprint(id string, object api.CheckObject, finding api.CheckFinding) string {
identity := strings.Join([]string{
id, object.Kind, object.Namespace, object.Name, finding.Container,
}, sarifIdentitySep)
sum := sha256.Sum256([]byte(identity))
return hex.EncodeToString(sum[:])
}
func sarifSuppressions(finding api.CheckFinding) []SARIFSuppression {
if !finding.Muted {
return nil
}
return []SARIFSuppression{{Kind: sarifMuteKind, Justification: finding.Reason}}
}