fixed jira formatting issue

This commit is contained in:
knakul853 2025-05-31 21:22:27 +05:30
parent a4859df5e9
commit 51cf69d7c4
5 changed files with 200 additions and 18 deletions

View File

@ -28,6 +28,10 @@ func (markdownFormatter MarkdownFormatter) CreateHorizontalLine() string {
return CreateHorizontalLine()
}
func (markdownFormatter MarkdownFormatter) FormatLineBreaks(text string) string {
return strings.ReplaceAll(text, "\n", "<br>")
}
// escapeCodeBlockMarkdown only escapes the bare minimum characters needed
// for code blocks and other sections where readability is important
//

View File

@ -6,4 +6,5 @@ type ResultFormatter interface {
CreateTable(headers []string, rows [][]string) (string, error)
CreateLink(title string, url string) string
CreateHorizontalLine() string
FormatLineBreaks(text string) string
}

View File

@ -9,7 +9,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
unitutils "github.com/projectdiscovery/utils/unit"
@ -171,20 +170,20 @@ func CreateTemplateInfoTable(templateInfo *model.Info, formatter ResultFormatter
}
if !utils.IsBlank(templateInfo.Description) {
rows = append(rows, []string{"Description", lineBreakToHTML(templateInfo.Description)})
rows = append(rows, []string{"Description", formatter.FormatLineBreaks(templateInfo.Description)})
}
if !utils.IsBlank(templateInfo.Remediation) {
rows = append(rows, []string{"Remediation", lineBreakToHTML(templateInfo.Remediation)})
rows = append(rows, []string{"Remediation", formatter.FormatLineBreaks(templateInfo.Remediation)})
}
classification := templateInfo.Classification
if classification != nil {
if classification.CVSSMetrics != "" {
rows = append(rows, []string{"CVSS-Metrics", generateCVSSMetricsFromClassification(classification)})
rows = append(rows, []string{"CVSS-Metrics", generateCVSSMetricsFromClassification(classification, formatter)})
}
rows = append(rows, generateCVECWEIDLinksFromClassification(classification)...)
rows = append(rows, generateCVECWEIDLinksFromClassification(classification, formatter)...)
rows = append(rows, []string{"CVSS-Score", strconv.FormatFloat(classification.CVSSScore, 'f', 2, 64)})
}
@ -202,7 +201,7 @@ func CreateTemplateInfoTable(templateInfo *model.Info, formatter ResultFormatter
return table
}
func generateCVSSMetricsFromClassification(classification *model.Classification) string {
func generateCVSSMetricsFromClassification(classification *model.Classification, formatter ResultFormatter) string {
var cvssLinkPrefix string
if strings.Contains(classification.CVSSMetrics, "CVSS:3.0") {
cvssLinkPrefix = "https://www.first.org/cvss/calculator/3.0#"
@ -213,11 +212,11 @@ func generateCVSSMetricsFromClassification(classification *model.Classification)
if cvssLinkPrefix == "" {
return classification.CVSSMetrics
} else {
return util.CreateLink(classification.CVSSMetrics, cvssLinkPrefix+classification.CVSSMetrics)
return formatter.CreateLink(classification.CVSSMetrics, cvssLinkPrefix+classification.CVSSMetrics)
}
}
func generateCVECWEIDLinksFromClassification(classification *model.Classification) [][]string {
func generateCVECWEIDLinksFromClassification(classification *model.Classification, formatter ResultFormatter) [][]string {
cwes := classification.CWEID.ToSlice()
cweIDs := make([]string, 0, len(cwes))
@ -226,7 +225,7 @@ func generateCVECWEIDLinksFromClassification(classification *model.Classificatio
if len(parts) != 2 {
continue
}
cweIDs = append(cweIDs, util.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", parts[1])))
cweIDs = append(cweIDs, formatter.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", parts[1])))
}
var rows [][]string
@ -238,7 +237,7 @@ func generateCVECWEIDLinksFromClassification(classification *model.Classificatio
cves := classification.CVEID.ToSlice()
cveIDs := make([]string, 0, len(cves))
for _, value := range cves {
cveIDs = append(cveIDs, util.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", value)))
cveIDs = append(cveIDs, formatter.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", value)))
}
if len(cveIDs) > 0 {
rows = append(rows, []string{"CVE-ID", strings.Join(cveIDs, ",")})

View File

@ -35,17 +35,57 @@ func (jiraFormatter *Formatter) CreateCodeBlock(title string, content string, _
}
func (jiraFormatter *Formatter) CreateTable(headers []string, rows [][]string) (string, error) {
table, err := jiraFormatter.MarkdownFormatter.CreateTable(headers, rows)
if err != nil {
return "", err
if len(headers) == 0 {
return "", fmt.Errorf("no headers provided")
}
tableRows := strings.Split(table, "\n")
tableRowsWithoutHeaderSeparator := append(tableRows[:1], tableRows[2:]...)
return strings.Join(tableRowsWithoutHeaderSeparator, "\n"), nil
var builder strings.Builder
// Create header row with leading and trailing pipes
builder.WriteString("| ")
builder.WriteString(strings.Join(headers, " | "))
builder.WriteString(" |")
builder.WriteString("\n")
// Create separator row with leading and trailing pipes
separators := make([]string, len(headers))
for i := range separators {
separators[i] = "-----------"
}
builder.WriteString("|")
builder.WriteString(strings.Join(separators, "|"))
builder.WriteString("|")
builder.WriteString("\n")
// Create data rows with leading and trailing pipes
for _, row := range rows {
builder.WriteString("| ")
if len(row) < len(headers) {
extendedRow := make([]string, len(headers))
copy(extendedRow, row)
builder.WriteString(strings.Join(extendedRow, " | "))
} else if len(row) > len(headers) {
builder.WriteString(strings.Join(row[:len(headers)], " | "))
} else {
builder.WriteString(strings.Join(row, " | "))
}
builder.WriteString(" |")
builder.WriteString("\n")
}
return builder.String(), nil
}
func (jiraFormatter *Formatter) CreateHorizontalLine() string {
return "----\n"
}
func (jiraFormatter *Formatter) FormatLineBreaks(text string) string {
return strings.ReplaceAll(text, "\n", "\\\\")
}
func (jiraFormatter *Formatter) CreateLink(title string, url string) string {
return fmt.Sprintf("[%s|%s]", title, url)
return fmt.Sprintf("[%s](%s)", title, url)
}
// Integration is a client for an issue tracker integration

View File

@ -3,10 +3,13 @@ package jira
import (
"strings"
"testing"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
"github.com/stretchr/testify/require"
)
@ -14,7 +17,18 @@ import (
func TestLinkCreation(t *testing.T) {
jiraIntegration := &Integration{}
link := jiraIntegration.CreateLink("ProjectDiscovery", "https://projectdiscovery.io")
require.Equal(t, "[ProjectDiscovery|https://projectdiscovery.io]", link)
require.Equal(t, "[ProjectDiscovery](https://projectdiscovery.io)", link)
}
func TestLinkCreationWithSpecialCharacters(t *testing.T) {
jiraIntegration := &Integration{}
link := jiraIntegration.CreateLink("Nuclei [v3.4.4]", "https://github.com/projectdiscovery/nuclei")
expected := "[Nuclei [v3.4.4]](https://github.com/projectdiscovery/nuclei)"
require.Equal(t, expected, link)
require.NotContains(t, link, "%5D")
require.NotContains(t, link, "%5B")
}
func TestHorizontalLineCreation(t *testing.T) {
@ -34,6 +48,7 @@ func TestTableCreation(t *testing.T) {
require.Nil(t, err)
expected := `| key | value |
|-----------|-----------|
| a | b |
| c | |
| d | e |
@ -41,6 +56,129 @@ func TestTableCreation(t *testing.T) {
require.Equal(t, expected, table)
}
func TestTableCreationWithComplexData(t *testing.T) {
jiraIntegration := &Integration{}
table, err := jiraIntegration.CreateTable([]string{"Key", "Value"}, [][]string{
{"Name", "GraphQL CSRF / GET method"},
{"Authors", "Dolev Farhi"},
{"Tags", "graphql, misconfig"},
{"Severity", "info"},
})
require.Nil(t, err)
require.Contains(t, table, "| Key | Value |")
require.Contains(t, table, "|-----------|-----------|")
require.Contains(t, table, "| Name | GraphQL CSRF / GET method |")
require.Contains(t, table, "GraphQL CSRF")
require.NotContains(t, table, "| ame |")
require.NotContains(t, table, "| uthors |")
}
func TestFormatLineBreaks(t *testing.T) {
jiraIntegration := &Integration{}
input := "Line 1\nLine 2\nLine 3"
result := jiraIntegration.FormatLineBreaks(input)
expected := "Line 1\\\\Line 2\\\\Line 3"
require.Equal(t, expected, result)
require.NotContains(t, result, "<br>")
}
func TestFormatLineBreaksWithMultipleBreaks(t *testing.T) {
jiraIntegration := &Integration{}
input := "Cross Site Request Forgery happens when an external website gains ability to make API calls impersonating an user.\nAllowing API calls through GET requests can lead to CSRF attacks."
result := jiraIntegration.FormatLineBreaks(input)
expected := "Cross Site Request Forgery happens when an external website gains ability to make API calls impersonating an user.\\\\Allowing API calls through GET requests can lead to CSRF attacks."
require.Equal(t, expected, result)
}
func TestCompleteReportGeneration(t *testing.T) {
jiraIntegration := &Integration{}
event := &output.ResultEvent{
TemplateID: "graphql-get-method",
Info: model.Info{
Name: "GraphQL CSRF / GET method",
Authors: stringslice.StringSlice{Value: []string{"Dolev Farhi"}},
Tags: stringslice.StringSlice{Value: []string{"graphql", "misconfig"}},
SeverityHolder: severity.Holder{Severity: severity.Info},
Description: "Cross Site Request Forgery happens when an external website gains ability to make API calls impersonating an user.\nAllowing API calls through GET requests can lead to CSRF attacks.",
Reference: stringslice.NewRawStringSlice([]string{
"https://graphql.org/learn/serving-over-http/#get-request",
"https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application",
}),
},
Host: "example.com",
Matched: "example.com/graphql",
Timestamp: time.Date(2025, 5, 31, 12, 0, 0, 0, time.UTC),
Type: "http",
}
description := format.CreateReportDescription(event, jiraIntegration, false)
require.Contains(t, description, "*Details*: *graphql-get-method* matched at example.com")
require.Contains(t, description, "*Protocol*: HTTP")
require.Contains(t, description, "*Template Information*")
require.Contains(t, description, "| Key | Value |")
require.Contains(t, description, "|-----------|-----------|")
require.Contains(t, description, "| Name | GraphQL CSRF / GET method |")
require.Contains(t, description, "impersonating an user.\\\\Allowing API calls")
require.NotContains(t, description, "<br>")
require.Contains(t, description, "[Nuclei")
require.Contains(t, description, "](https://github.com/projectdiscovery/nuclei)")
require.NotContains(t, description, "%5D")
require.NotContains(t, description, "%5B")
require.Contains(t, description, "References:")
require.Contains(t, description, "https://graphql.org/learn/serving-over-http/#get-request")
}
func TestReportWithCVELinks(t *testing.T) {
jiraIntegration := &Integration{}
event := &output.ResultEvent{
TemplateID: "test-cve",
Info: model.Info{
Name: "Test CVE Template",
Authors: stringslice.StringSlice{Value: []string{"test-author"}},
Tags: stringslice.StringSlice{Value: []string{"cve", "test"}},
SeverityHolder: severity.Holder{Severity: severity.High},
Description: "Test template with CVE links",
Classification: &model.Classification{
CVEID: stringslice.StringSlice{Value: []string{"CVE-2021-44228"}},
CWEID: stringslice.StringSlice{Value: []string{"CWE-502"}},
CVSSMetrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
CVSSScore: 10.0,
},
},
Host: "example.com",
Matched: "example.com/test",
Timestamp: time.Date(2025, 5, 31, 12, 0, 0, 0, time.UTC),
Type: "http",
}
description := format.CreateReportDescription(event, jiraIntegration, false)
require.Contains(t, description, "[CVE-2021-44228](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228)")
require.Contains(t, description, "[CWE-502](https://cwe.mitre.org/data/definitions/502.html)")
require.Contains(t, description, "[CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H](https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)")
require.NotContains(t, description, "|https://")
require.NotContains(t, description, "%5D")
}
func Test_ShouldFilter_Tracker(t *testing.T) {
jiraIntegration := &Integration{
options: &Options{AllowList: &filters.Filter{