sarif.go 4.7 KB

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