Ctrl K
ComunidadGuías…

internal/checks/sarif.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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}}
}