sarif.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package report
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "github.com/zricethezav/gitleaks/v8/config"
  7. )
  8. type SarifReporter struct {
  9. OrderedRules []config.Rule
  10. }
  11. var _ Reporter = (*SarifReporter)(nil)
  12. func (r *SarifReporter) Write(w io.WriteCloser, findings []Finding) error {
  13. sarif := Sarif{
  14. Schema: "https://json.schemastore.org/sarif-2.1.0.json",
  15. Version: "2.1.0",
  16. Runs: r.getRuns(findings),
  17. }
  18. encoder := json.NewEncoder(w)
  19. encoder.SetIndent("", " ")
  20. return encoder.Encode(sarif)
  21. }
  22. func (r *SarifReporter) getRuns(findings []Finding) []Runs {
  23. return []Runs{
  24. {
  25. Tool: r.getTool(),
  26. Results: getResults(findings),
  27. },
  28. }
  29. }
  30. func (r *SarifReporter) getTool() Tool {
  31. tool := Tool{
  32. Driver: Driver{
  33. Name: driver,
  34. SemanticVersion: version,
  35. InformationUri: "https://github.com/gitleaks/gitleaks",
  36. Rules: r.getRules(),
  37. },
  38. }
  39. // if this tool has no rules, ensure that it is represented as [] instead of null/nil
  40. if hasEmptyRules(tool) {
  41. tool.Driver.Rules = make([]Rules, 0)
  42. }
  43. return tool
  44. }
  45. func hasEmptyRules(tool Tool) bool {
  46. return len(tool.Driver.Rules) == 0
  47. }
  48. func (r *SarifReporter) getRules() []Rules {
  49. // TODO for _, rule := range cfg.Rules {
  50. var rules []Rules
  51. for _, rule := range r.OrderedRules {
  52. rules = append(rules, Rules{
  53. ID: rule.RuleID,
  54. Description: ShortDescription{
  55. Text: rule.Description,
  56. },
  57. })
  58. }
  59. return rules
  60. }
  61. func messageText(f Finding) string {
  62. if f.Commit == "" {
  63. return fmt.Sprintf("%s has detected secret for file %s.", f.RuleID, f.File)
  64. }
  65. return fmt.Sprintf("%s has detected secret for file %s at commit %s.", f.RuleID, f.File, f.Commit)
  66. }
  67. func getResults(findings []Finding) []Results {
  68. results := []Results{}
  69. for _, f := range findings {
  70. r := Results{
  71. Message: Message{
  72. Text: messageText(f),
  73. },
  74. RuleId: f.RuleID,
  75. Locations: getLocation(f),
  76. // This information goes in partial fingerprings until revision
  77. // data can be added somewhere else
  78. PartialFingerPrints: PartialFingerPrints{
  79. CommitSha: f.Commit,
  80. Email: f.Email,
  81. CommitMessage: f.Message,
  82. Date: f.Date,
  83. Author: f.Author,
  84. },
  85. Properties: Properties{
  86. Tags: f.Tags,
  87. },
  88. }
  89. results = append(results, r)
  90. }
  91. return results
  92. }
  93. func getLocation(f Finding) []Locations {
  94. uri := f.File
  95. if f.SymlinkFile != "" {
  96. uri = f.SymlinkFile
  97. }
  98. return []Locations{
  99. {
  100. PhysicalLocation: PhysicalLocation{
  101. ArtifactLocation: ArtifactLocation{
  102. URI: uri,
  103. },
  104. Region: Region{
  105. StartLine: f.StartLine,
  106. EndLine: f.EndLine,
  107. StartColumn: f.StartColumn,
  108. EndColumn: f.EndColumn,
  109. Snippet: Snippet{
  110. Text: f.Secret,
  111. },
  112. },
  113. },
  114. },
  115. }
  116. }
  117. type PartialFingerPrints struct {
  118. CommitSha string `json:"commitSha"`
  119. Email string `json:"email"`
  120. Author string `json:"author"`
  121. Date string `json:"date"`
  122. CommitMessage string `json:"commitMessage"`
  123. }
  124. type Sarif struct {
  125. Schema string `json:"$schema"`
  126. Version string `json:"version"`
  127. Runs []Runs `json:"runs"`
  128. }
  129. type ShortDescription struct {
  130. Text string `json:"text"`
  131. }
  132. type FullDescription struct {
  133. Text string `json:"text"`
  134. }
  135. type Rules struct {
  136. ID string `json:"id"`
  137. Description ShortDescription `json:"shortDescription"`
  138. }
  139. type Driver struct {
  140. Name string `json:"name"`
  141. SemanticVersion string `json:"semanticVersion"`
  142. InformationUri string `json:"informationUri"`
  143. Rules []Rules `json:"rules"`
  144. }
  145. type Tool struct {
  146. Driver Driver `json:"driver"`
  147. }
  148. type Message struct {
  149. Text string `json:"text"`
  150. }
  151. type ArtifactLocation struct {
  152. URI string `json:"uri"`
  153. }
  154. type Region struct {
  155. StartLine int `json:"startLine"`
  156. StartColumn int `json:"startColumn"`
  157. EndLine int `json:"endLine"`
  158. EndColumn int `json:"endColumn"`
  159. Snippet Snippet `json:"snippet"`
  160. }
  161. type Snippet struct {
  162. Text string `json:"text"`
  163. }
  164. type PhysicalLocation struct {
  165. ArtifactLocation ArtifactLocation `json:"artifactLocation"`
  166. Region Region `json:"region"`
  167. }
  168. type Locations struct {
  169. PhysicalLocation PhysicalLocation `json:"physicalLocation"`
  170. }
  171. type Properties struct {
  172. Tags []string `json:"tags"`
  173. }
  174. type Results struct {
  175. Message Message `json:"message"`
  176. RuleId string `json:"ruleId"`
  177. Locations []Locations `json:"locations"`
  178. PartialFingerPrints `json:"partialFingerprints"`
  179. Properties Properties `json:"properties"`
  180. }
  181. type Runs struct {
  182. Tool Tool `json:"tool"`
  183. Results []Results `json:"results"`
  184. }