Repository: hahwul/dalfox

Issue:
Add markdown in `--report-format` flag
## Information
- Start: 2025-07-06 09:04:39.479011 +0900 KST m=+0.006816876
- End: 2025-07-06 09:04:46.407647 +0900 KST m=+6.935403960
- Duration: 6.928587084s

## Parameter Analysis
| Param | Type | Reflected | R-Point | R-Code | Chars |
|---|---|---|---|---|---|
| query | URL | true | Injected: /inHTML-none(1) | 13 line:  were found for &lt;b&gt;ABDalfox&lt;/b&gt;. &lt;a href='?'&gt;Try again | ( \| ' " ) - ; &gt; \ { [ + ` = $ } &lt; : , ] . |

## XSS PoCs
| # | Type | Severity | Method | Param | Inject-Type | CWE |
|---|---|---|---|---|---|---|
| [PoC1](#PoC1) | R | Medium | GET | query | inHTML-URL | CWE-79 |
| [PoC2](#PoC2) | R | Medium | GET | query | inHTML-URL | CWE-79 |
| [PoC3](#PoC3) | V | High | GET | query | inHTML-URL | CWE-79 |

### PoC1
```
https://xss-game.appspot.com/level1/frame?query=AB%3Cxmp%3E%3Cp+title%3D%22%3C%2Fxmp%3E%3Csvg%2Fonload%3Dalert%281%29%3E
```

### PoC2
```
https://xss-game.appspot.com/level1/frame?query=AB%27%3E%3Cvideo%3E%3Csource+onerror%3Dalert%281%29%3E%3C%2Fvideo%3E
```

### PoC3
```
https://xss-game.appspot.com/level1/frame?query=AB%22%3E%3CIMG+SRC%3Dx+oninvalid%3D%22alert%28String.fromCharCode%2888%2C83%2C83%29%29%22+class%3Ddalfox%3E
```

Discussion:

Solution patch:
diff --git a/cmd/args.go b/cmd/args.go
index c83da09..a70205f 100644
--- a/cmd/args.go
+++ b/cmd/args.go
@@ -30,7 +30,7 @@ type Args struct {
 	RemoteWordlists           string // Remote wordlist sources
 	OnlyPoC                   string // Show only PoC for specific patterns
 	PoCType                   string // PoC output format
-	ReportFormat              string // Report format (plain, json)
+	ReportFormat              string // Report format (plain, json, markdown, md)
 	HarFilePath               string // Path to save HAR files
 	CustomBlindXSSPayloadFile string // Path to custom blind XSS payload file
 
diff --git a/cmd/root.go b/cmd/root.go
index 70f7c35..684a21f 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -114,7 +114,7 @@ func init() {
 	rootCmd.PersistentFlags().StringVar(&args.RemoteWordlists, "remote-wordlists", "", "Use remote wordlists for parameter mining. Supported: burp, assetnote. Example: --remote-wordlists 'burp'")
 	rootCmd.PersistentFlags().StringVar(&args.OnlyPoC, "only-poc", "", "Show only the PoC code for the specified pattern. Supported: g (grep), r (reflected), v (verified). Example: --only-poc 'g,v'")
 	rootCmd.PersistentFlags().StringVar(&args.PoCType, "poc-type", "plain", "Select the PoC type. Supported: plain, curl, httpie, http-request. Example: --poc-type 'curl'")
-	rootCmd.PersistentFlags().StringVar(&args.ReportFormat, "report-format", "plain", "Set the format of the report. Supported: plain, json. Example: --report-format 'json'")
+	rootCmd.PersistentFlags().StringVar(&args.ReportFormat, "report-format", "plain", "Set the format of the report. Supported: plain, json, markdown, md. Example: --report-format 'json'")
 	rootCmd.PersistentFlags().StringVar(&args.HarFilePath, "har-file-path", "", "Specify the path to save HAR files of scan requests. Example: --har-file-path 'scan.har'")
 
 	// Int
diff --git a/docs/page/output-handling.md b/docs/page/output-handling.md
index 2a8c178..d32b53a 100644
--- a/docs/page/output-handling.md
+++ b/docs/page/output-handling.md
@@ -127,11 +127,25 @@ This generates structured JSON data that can be easily parsed by scripts or impo
 
 ### Detailed Report Generation
 
-For comprehensive reporting:
+Dalfox supports generating detailed reports in various formats.
 
+**JSON Report:**
+
+For a structured JSON report:
 ```bash
 dalfox url http://example.com/vulnerable.php --report --report-format json -o detailed_report.json
 ```
+This is useful for machine-readable detailed output.
+
+**Markdown Report:**
+
+For a human-readable Markdown report:
+```bash
+dalfox url http://example.com/vulnerable.php --report --report-format markdown -o detailed_report.md
+# You can also use 'md' as an alias for markdown
+dalfox url http://example.com/vulnerable.php --report --report-format md -o detailed_report.md
+```
+This format is convenient for documentation or quick sharing of findings.
 
 ## HTTP Archive (HAR) Integration
 
diff --git a/internal/report/report_markdown.go b/internal/report/report_markdown.go
new file mode 100644
index 0000000..3f5290d
--- /dev/null
+++ b/internal/report/report_markdown.go
@@ -0,0 +1,58 @@
+package report
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/hahwul/dalfox/v2/pkg/model"
+)
+
+// GenerateMarkdownReport creates a report in Markdown format
+func GenerateMarkdownReport(scanResult model.Result, options model.Options) string {
+	var report strings.Builder
+	sanitize := func(s string) string {
+		return strings.NewReplacer(
+			"|", `\|`,
+			"<", "&lt;",
+			">", "&gt;",
+		).Replace(s)
+	}
+
+	report.WriteString("## Information\n")
+	report.WriteString(fmt.Sprintf("- Start: %s\n", scanResult.StartTime.String()))
+	report.WriteString(fmt.Sprintf("- End: %s\n", scanResult.EndTime.String()))
+	report.WriteString(fmt.Sprintf("- Duration: %s\n\n", scanResult.Duration.String()))
+
+	report.WriteString("## Parameter Analysis\n")
+	report.WriteString("| Param | Type | Reflected | R-Point | R-Code | Chars |\n")
+	report.WriteString("|---|---|---|---|---|---|\n")
+	for _, v := range scanResult.Params {
+		chars := strings.Join(v.Chars, " ")
+		reflected := "false"
+		if v.Reflected {
+			reflected = "true"
+		}
+		report.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s |\n", sanitize(v.Name), sanitize(v.Type), reflected, sanitize(v.ReflectedPoint), sanitize(v.ReflectedCode), sanitize(chars)))
+	}
+	report.WriteString("\n")
+
+	report.WriteString("## XSS PoCs\n")
+	if len(scanResult.PoCs) > 0 {
+		report.WriteString("| # | Type | Severity | Method | Param | Inject-Type | CWE |\n")
+		report.WriteString("|---|---|---|---|---|---|---|\n")
+		for i, v := range scanResult.PoCs {
+			idx := i + 1
+			report.WriteString(fmt.Sprintf("| [PoC%d](#PoC%d) | %s | %s | %s | %s | %s | %s |\n", idx, idx, v.Type, v.Severity, v.Method, v.Param, v.InjectType, v.CWE))
+		}
+		report.WriteString("\n")
+		for i, v := range scanResult.PoCs {
+			idx := i + 1
+			report.WriteString(fmt.Sprintf("### PoC%d\n", idx))
+			report.WriteString(fmt.Sprintf("```\n%s\n```\n\n", v.Data))
+		}
+	} else {
+		report.WriteString("No XSS vulnerabilities found.\n\n")
+	}
+
+	return report.String()
+}
diff --git a/pkg/scanning/scan.go b/pkg/scanning/scan.go
index 141c6b9..7c797ef 100644
--- a/pkg/scanning/scan.go
+++ b/pkg/scanning/scan.go
@@ -277,6 +277,9 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 			if err == nil {
 				fmt.Println(string(jobject))
 			}
+		} else if options.ReportFormat == "markdown" || options.ReportFormat == "md" {
+			markdownReport := report.GenerateMarkdownReport(scanResult, options)
+			fmt.Println(markdownReport)
 		} else {
 			report.GenerateReport(scanResult, options)
 		}



Test patch:
diff --git a/internal/report/report_markdown_test.go b/internal/report/report_markdown_test.go
new file mode 100644
index 0000000..fb52d50
--- /dev/null
+++ b/internal/report/report_markdown_test.go
@@ -0,0 +1,255 @@
+package report
+
+import (
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/hahwul/dalfox/v2/pkg/model"
+	"github.com/logrusorgru/aurora"
+)
+
+func TestGenerateMarkdownReport(t *testing.T) {
+	options := model.Options{
+		AuroraObject: aurora.NewAurora(true), // Or false, depending on whether you want color in tests
+	}
+	scanResult := model.Result{
+		StartTime: time.Date(2023, 10, 26, 10, 0, 0, 0, time.UTC),
+		EndTime:   time.Date(2023, 10, 26, 11, 0, 0, 0, time.UTC),
+		Duration:  1 * time.Hour,
+		Params: []model.ParamResult{
+			{
+				Name:           "param1",
+				Type:           "query",
+				Reflected:      true,
+				ReflectedPoint: "body",
+				ReflectedCode:  "param1_reflected",
+				Chars:          []string{"'", "\""},
+			},
+			{
+				Name:           "param2",
+				Type:           "header",
+				Reflected:      false,
+				ReflectedPoint: "",
+				ReflectedCode:  "",
+				Chars:          []string{},
+			},
+		},
+		PoCs: []model.PoC{
+			{
+				Type:       "XSS",
+				Severity:   "High",
+				Method:     "GET",
+				Param:      "param1",
+				InjectType: "inHTML",
+				CWE:        "CWE-79",
+				Data:       "<script>alert(1)</script>",
+			},
+		},
+	}
+
+	expectedReport := `## Information
+- Start: 2023-10-26 10:00:00 +0000 UTC
+- End: 2023-10-26 11:00:00 +0000 UTC
+- Duration: 1h0m0s
+
+## Parameter Analysis
+| Param | Type | Reflected | R-Point | R-Code | Chars |
+|---|---|---|---|---|---|
+| param1 | query | true | body | param1_reflected | ' " |
+| param2 | header | false |  |  |  |
+
+## XSS PoCs
+| # | Type | Severity | Method | Param | Inject-Type | CWE |
+|---|---|---|---|---|---|---|
+| #0 | XSS | High | GET | param1 | inHTML | CWE-79 |
+
+### PoC #0
+` + "```\n<script>alert(1)</script>\n```" + `
+
+` // Adding the code block directly as it contains backticks
+
+	report := GenerateMarkdownReport(scanResult, options)
+
+	if report != expectedReport {
+		t.Errorf("GenerateMarkdownReport() output did not match expected.\nGot:\n%s\n\nWant:\n%s", report, expectedReport)
+	}
+}
+
+func TestGenerateMarkdownReport_NoPoCs(t *testing.T) {
+	options := model.Options{
+		AuroraObject: aurora.NewAurora(true),
+	}
+	scanResult := model.Result{
+		StartTime: time.Date(2023, 10, 26, 10, 0, 0, 0, time.UTC),
+		EndTime:   time.Date(2023, 10, 26, 11, 0, 0, 0, time.UTC),
+		Duration:  1 * time.Hour,
+		Params: []model.ParamResult{
+			{
+				Name:           "param1",
+				Type:           "query",
+				Reflected:      true,
+				ReflectedPoint: "body",
+				ReflectedCode:  "param1_reflected",
+				Chars:          []string{"'", "\""},
+			},
+		},
+		PoCs: []model.PoC{}, // No PoCs
+	}
+
+	expectedReport := `## Information
+- Start: 2023-10-26 10:00:00 +0000 UTC
+- End: 2023-10-26 11:00:00 +0000 UTC
+- Duration: 1h0m0s
+
+## Parameter Analysis
+| Param | Type | Reflected | R-Point | R-Code | Chars |
+|---|---|---|---|---|---|
+| param1 | query | true | body | param1_reflected | ' " |
+
+## XSS PoCs
+No XSS vulnerabilities found.
+
+`
+
+	report := GenerateMarkdownReport(scanResult, options)
+
+	if report != expectedReport {
+		t.Errorf("GenerateMarkdownReport() with no PoCs output did not match expected.\nGot:\n%s\n\nWant:\n%s", report, expectedReport)
+	}
+}
+
+func TestGenerateMarkdownReport_NoParams(t *testing.T) {
+	options := model.Options{
+		AuroraObject: aurora.NewAurora(true),
+	}
+	scanResult := model.Result{
+		StartTime: time.Date(2023, 10, 26, 10, 0, 0, 0, time.UTC),
+		EndTime:   time.Date(2023, 10, 26, 11, 0, 0, 0, time.UTC),
+		Duration:  1 * time.Hour,
+		Params:    []model.ParamResult{}, // No Params
+		PoCs: []model.PoC{
+			{
+				Type:       "XSS",
+				Severity:   "High",
+				Method:     "GET",
+				Param:      "param1",
+				InjectType: "inHTML",
+				CWE:        "CWE-79",
+				Data:       "<script>alert(1)</script>",
+			},
+		},
+	}
+
+	expectedReport := `## Information
+- Start: 2023-10-26 10:00:00 +0000 UTC
+- End: 2023-10-26 11:00:00 +0000 UTC
+- Duration: 1h0m0s
+
+## Parameter Analysis
+| Param | Type | Reflected | R-Point | R-Code | Chars |
+|---|---|---|---|---|---|
+
+## XSS PoCs
+| # | Type | Severity | Method | Param | Inject-Type | CWE |
+|---|---|---|---|---|---|---|
+| #0 | XSS | High | GET | param1 | inHTML | CWE-79 |
+
+### PoC #0
+` + "```\n<script>alert(1)</script>\n```" + `
+
+`
+	report := GenerateMarkdownReport(scanResult, options)
+
+	if report != expectedReport {
+		t.Errorf("GenerateMarkdownReport() with no Params output did not match expected.\nGot:\n%s\n\nWant:\n%s", report, expectedReport)
+	}
+}
+
+func TestGenerateMarkdownReport_EmptyResult(t *testing.T) {
+	options := model.Options{
+		AuroraObject: aurora.NewAurora(true),
+	}
+	scanResult := model.Result{
+		StartTime: time.Date(2023, 10, 26, 10, 0, 0, 0, time.UTC),
+		EndTime:   time.Date(2023, 10, 26, 11, 0, 0, 0, time.UTC),
+		Duration:  1 * time.Hour,
+		Params:    []model.ParamResult{}, // No Params
+		PoCs:      []model.PoC{},         // No PoCs
+	}
+
+	expectedReport := `## Information
+- Start: 2023-10-26 10:00:00 +0000 UTC
+- End: 2023-10-26 11:00:00 +0000 UTC
+- Duration: 1h0m0s
+
+## Parameter Analysis
+| Param | Type | Reflected | R-Point | R-Code | Chars |
+|---|---|---|---|---|---|
+
+## XSS PoCs
+No XSS vulnerabilities found.
+
+`
+	report := GenerateMarkdownReport(scanResult, options)
+
+	if report != expectedReport {
+		t.Errorf("GenerateMarkdownReport() with empty result output did not match expected.\nGot:\n%s\n\nWant:\n%s", report, expectedReport)
+	}
+}
+
+func TestGenerateMarkdownReport_MultiplePoCs(t *testing.T) {
+	options := model.Options{
+		AuroraObject: aurora.NewAurora(true),
+	}
+	scanResult := model.Result{
+		StartTime: time.Date(2023, 10, 26, 10, 0, 0, 0, time.UTC),
+		EndTime:   time.Date(2023, 10, 26, 11, 0, 0, 0, time.UTC),
+		Duration:  1 * time.Hour,
+		Params: []model.ParamResult{
+			{
+				Name:           "param1",
+				Type:           "query",
+				Reflected:      true,
+				ReflectedPoint: "body",
+				ReflectedCode:  "param1_reflected",
+				Chars:          []string{"'", "\""},
+			},
+		},
+		PoCs: []model.PoC{
+			{
+				Type:       "XSS",
+				Severity:   "High",
+				Method:     "GET",
+				Param:      "param1",
+				InjectType: "inHTML",
+				CWE:        "CWE-79",
+				Data:       "<script>alert(1)</script>",
+			},
+			{
+				Type:       "Reflected XSS",
+				Severity:   "Medium",
+				Method:     "POST",
+				Param:      "q",
+				InjectType: "inJs",
+				CWE:        "CWE-79",
+				Data:       "';alert(2);'",
+			},
+		},
+	}
+
+	report := GenerateMarkdownReport(scanResult, options)
+
+	if !strings.Contains(report, "### PoC #0") {
+		t.Errorf("Report does not contain PoC #0")
+	}
+	if !strings.Contains(report, "<script>alert(1)</script>") {
+		t.Errorf("Report does not contain data for PoC #0")
+	}
+	if !strings.Contains(report, "### PoC #1") {
+		t.Errorf("Report does not contain PoC #1")
+	}
+	if !strings.Contains(report, "';alert(2);'") {
+		t.Errorf("Report does not contain data for PoC #1")
+	}
+}