From f53b945a5c3d88807a6797db66f732f961fc03f9 Mon Sep 17 00:00:00 2001 From: ayuxsec <249073442+ayuxsec@users.noreply.github.com> Date: Fri, 26 Dec 2025 17:37:29 +0530 Subject: [PATCH 1/7] Add Markdown output support - Introduce `MarkDownOutput` flag and CLI option (`-markdown` / `-md`). - Implement `Result.MarkdownOutput` to generate a formatted Markdown table with optional title and CDN columns, and a response body preview. - Add markdown escaping helper. - Update runner to create `.md` output file, handle markdown generation per result, and write to file or stdout. - Adjust option parsing and related logic to include markdown handling. --- runner/md_output.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ runner/options.go | 2 ++ runner/runner.go | 24 ++++++++++++++-- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 runner/md_output.go diff --git a/runner/md_output.go b/runner/md_output.go new file mode 100644 index 00000000..76e24218 --- /dev/null +++ b/runner/md_output.go @@ -0,0 +1,67 @@ +package runner + +import ( + "fmt" + "net/http" + "strings" +) + +func (r Result) MarkdownOutput(scanopts *ScanOptions) string { + var b strings.Builder + + // Table Header + b.WriteString("| URL | Status | Method | IP | Size | Words | Lines |") + if r.Title != "" { + b.WriteString(" Title |") + } + if r.CDNName != "" { + b.WriteString(" CDN |") + } + b.WriteString("\n") + + // Table Separator + b.WriteString("|---|---|---|---|---|---|---|") + if r.Title != "" { + b.WriteString("---|") + } + if r.CDNName != "" { + b.WriteString("---|") + } + b.WriteString("\n") + + // Table Data Row + fmt.Fprintf(&b, "| %s | `%d %s` | `%s` | `%s` | %d | %d | %d |", + r.URL, + r.StatusCode, http.StatusText(r.StatusCode), + r.Method, + r.HostIP, + r.ContentLength, + r.Words, + r.Lines) + + if r.Title != "" { + fmt.Fprintf(&b, " %s |", escapeMarkdown(r.Title)) + } + if r.CDNName != "" { + fmt.Fprintf(&b, " `%s` |", r.CDNName) + } + b.WriteString("\n\n") + + // Response Body Code Block + if r.BodyPreview != "" { + b.WriteString("**Response Body Preview:**\n") + b.WriteString("```text\n") + b.WriteString(r.BodyPreview) + b.WriteString("\n```\n") + } + + return b.String() +} + +func escapeMarkdown(s string) string { + replacer := strings.NewReplacer( + "|", "\\|", + "\n", " ", + ) + return strings.TrimSpace(replacer.Replace(s)) +} diff --git a/runner/options.go b/runner/options.go index c5c98d7d..e2b7a4c8 100644 --- a/runner/options.go +++ b/runner/options.go @@ -222,6 +222,7 @@ type Options struct { RespectHSTS bool StoreResponse bool JSONOutput bool + MarkDownOutput bool CSVOutput bool CSVOutputEncoding string PdcpAuth string @@ -478,6 +479,7 @@ func ParseOptions() *Options { flagSet.BoolVar(&options.CSVOutput, "csv", false, "store output in csv format"), flagSet.StringVarP(&options.CSVOutputEncoding, "csv-output-encoding", "csvo", "", "define output encoding"), flagSet.BoolVarP(&options.JSONOutput, "json", "j", false, "store output in JSONL(ines) format"), + flagSet.BoolVarP(&options.MarkDownOutput, "markdown", "md", false, "store output in Markdown table format"), flagSet.BoolVarP(&options.ResponseHeadersInStdout, "include-response-header", "irh", false, "include http response (headers) in JSON output (-json only)"), flagSet.BoolVarP(&options.ResponseInStdout, "include-response", "irr", false, "include http request/response (headers + body) in JSON output (-json only)"), flagSet.BoolVarP(&options.Base64ResponseInStdout, "include-response-base64", "irrb", false, "include base64 encoded http request/response in JSON output (-json only)"), diff --git a/runner/runner.go b/runner/runner.go index 2bf4dd44..1d707f16 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -815,7 +815,7 @@ func (r *Runner) RunEnumeration() { } }() - var plainFile, jsonFile, csvFile, indexFile, indexScreenshotFile *os.File + var plainFile, jsonFile, csvFile, mdFile, indexFile, indexScreenshotFile *os.File if r.options.Output != "" && r.options.OutputAll { plainFile = openOrCreateFile(r.options.Resume, r.options.Output) @@ -832,7 +832,17 @@ func (r *Runner) RunEnumeration() { }() } - jsonOrCsv := (r.options.JSONOutput || r.options.CSVOutput) + if r.options.Output != "" && r.options.MarkDownOutput { + mdFile = openOrCreateFile( + r.options.Resume, + r.options.Output+".md", + ) + defer func() { + _ = mdFile.Close() + }() + } + + jsonOrCsv := (r.options.JSONOutput || r.options.CSVOutput || r.options.MarkDownOutput) jsonAndCsv := (r.options.JSONOutput && r.options.CSVOutput) if r.options.Output != "" && plainFile == nil && !jsonOrCsv { plainFile = openOrCreateFile(r.options.Resume, r.options.Output) @@ -1228,6 +1238,16 @@ func (r *Runner) RunEnumeration() { } } + if r.options.MarkDownOutput { + row := resp.MarkdownOutput(&r.scanopts) + if !r.options.OutputAll { + gologger.Silent().Msgf("%s\n", row) + } + if mdFile != nil { + mdFile.WriteString(row + "\n") + } + } + for _, nextStep := range nextSteps { nextStep <- resp } From 15177106a4e2ab2c9fcc59f03fbaa5b1b55743f6 Mon Sep 17 00:00:00 2001 From: ayuxsec <249073442+ayuxsec@users.noreply.github.com> Date: Sat, 27 Dec 2025 00:31:56 +0530 Subject: [PATCH 2/7] fix(md output): emit markdown table header only once per run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split Markdown generation into `MarkdownHeader` and `MarkdownRow` for clearer separation. - Remove body‑preview block from Markdown rows. - Add guard in runner to write the header only once per run. --- runner/md_output.go | 21 ++++++++------------- runner/runner.go | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/runner/md_output.go b/runner/md_output.go index 76e24218..4b577774 100644 --- a/runner/md_output.go +++ b/runner/md_output.go @@ -6,10 +6,9 @@ import ( "strings" ) -func (r Result) MarkdownOutput(scanopts *ScanOptions) string { +func MarkdownHeader(r Result) string { var b strings.Builder - // Table Header b.WriteString("| URL | Status | Method | IP | Size | Words | Lines |") if r.Title != "" { b.WriteString(" Title |") @@ -19,7 +18,6 @@ func (r Result) MarkdownOutput(scanopts *ScanOptions) string { } b.WriteString("\n") - // Table Separator b.WriteString("|---|---|---|---|---|---|---|") if r.Title != "" { b.WriteString("---|") @@ -29,7 +27,12 @@ func (r Result) MarkdownOutput(scanopts *ScanOptions) string { } b.WriteString("\n") - // Table Data Row + return b.String() +} + +func (r Result) MarkdownRow(scanopts *ScanOptions) string { + var b strings.Builder + fmt.Fprintf(&b, "| %s | `%d %s` | `%s` | `%s` | %d | %d | %d |", r.URL, r.StatusCode, http.StatusText(r.StatusCode), @@ -45,15 +48,7 @@ func (r Result) MarkdownOutput(scanopts *ScanOptions) string { if r.CDNName != "" { fmt.Fprintf(&b, " `%s` |", r.CDNName) } - b.WriteString("\n\n") - - // Response Body Code Block - if r.BodyPreview != "" { - b.WriteString("**Response Body Preview:**\n") - b.WriteString("```text\n") - b.WriteString(r.BodyPreview) - b.WriteString("\n```\n") - } + b.WriteString("\n") return b.String() } diff --git a/runner/runner.go b/runner/runner.go index 1d707f16..f66c8488 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -816,6 +816,7 @@ func (r *Runner) RunEnumeration() { }() var plainFile, jsonFile, csvFile, mdFile, indexFile, indexScreenshotFile *os.File + markdownHeaderWritten := false // guard to prevent writing the header multiple times if r.options.Output != "" && r.options.OutputAll { plainFile = openOrCreateFile(r.options.Resume, r.options.Output) @@ -1239,12 +1240,24 @@ func (r *Runner) RunEnumeration() { } if r.options.MarkDownOutput { - row := resp.MarkdownOutput(&r.scanopts) + if !markdownHeaderWritten { + header := MarkdownHeader(resp) + if !r.options.OutputAll { + gologger.Silent().Msgf("%s", header) + } + if mdFile != nil { + mdFile.WriteString(header) + } + markdownHeaderWritten = true + } + + row := resp.MarkdownRow(&r.scanopts) + if !r.options.OutputAll { - gologger.Silent().Msgf("%s\n", row) + gologger.Silent().Msgf("%s", row) } if mdFile != nil { - mdFile.WriteString(row + "\n") + mdFile.WriteString(row) } } From cabc1aef20fa120e6d32ab04f510eeefc7356837 Mon Sep 17 00:00:00 2001 From: ayuxsec <249073442+ayuxsec@users.noreply.github.com> Date: Sat, 27 Dec 2025 00:44:57 +0530 Subject: [PATCH 3/7] =?UTF-8?q?fix(md=20output):=20always=20include=20Titl?= =?UTF-8?q?e=E2=80=AF&=E2=80=AFCDN=20columns=20and=20escape=20URLs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- runner/md_output.go | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/runner/md_output.go b/runner/md_output.go index 4b577774..3428210e 100644 --- a/runner/md_output.go +++ b/runner/md_output.go @@ -8,23 +8,9 @@ import ( func MarkdownHeader(r Result) string { var b strings.Builder - - b.WriteString("| URL | Status | Method | IP | Size | Words | Lines |") - if r.Title != "" { - b.WriteString(" Title |") - } - if r.CDNName != "" { - b.WriteString(" CDN |") - } + b.WriteString("| URL | Status | Method | IP | Size | Words | Lines | Title | CDN |") b.WriteString("\n") - - b.WriteString("|---|---|---|---|---|---|---|") - if r.Title != "" { - b.WriteString("---|") - } - if r.CDNName != "" { - b.WriteString("---|") - } + b.WriteString("|---|---|---|---|---|---|---|---|---|") b.WriteString("\n") return b.String() @@ -34,7 +20,7 @@ func (r Result) MarkdownRow(scanopts *ScanOptions) string { var b strings.Builder fmt.Fprintf(&b, "| %s | `%d %s` | `%s` | `%s` | %d | %d | %d |", - r.URL, + escapeMarkdown(r.URL), r.StatusCode, http.StatusText(r.StatusCode), r.Method, r.HostIP, @@ -44,12 +30,17 @@ func (r Result) MarkdownRow(scanopts *ScanOptions) string { if r.Title != "" { fmt.Fprintf(&b, " %s |", escapeMarkdown(r.Title)) + } else { + b.WriteString(" |") } + if r.CDNName != "" { fmt.Fprintf(&b, " `%s` |", r.CDNName) + } else { + b.WriteString(" |") } - b.WriteString("\n") + b.WriteString("\n") return b.String() } From b1f4809c2cc3c11cd56a445b0abc6935c45a14f0 Mon Sep 17 00:00:00 2001 From: ayuxsec <249073442+ayuxsec@users.noreply.github.com> Date: Sat, 27 Dec 2025 04:54:56 +0530 Subject: [PATCH 4/7] =?UTF-8?q?fix(mdoutput):=20Fix=20duplicate=20?= =?UTF-8?q?=E2=80=9C.md=E2=80=9D=20extension=20when=20using=20`-o`=20with?= =?UTF-8?q?=20`-md`=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Consolidate output‑type checks into a single `jsonOrCsvOrMD` variable. * Adjust file‑opening logic to add the “.md” suffix only when needed. * Update related conditionals to respect the new combined flag handling. * Ensure markdown output is correctly written without creating “.md.md” files. --- runner/runner.go | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/runner/runner.go b/runner/runner.go index f66c8488..e5c5414f 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -831,21 +831,15 @@ func (r *Runner) RunEnumeration() { defer func() { _ = csvFile.Close() }() - } - - if r.options.Output != "" && r.options.MarkDownOutput { - mdFile = openOrCreateFile( - r.options.Resume, - r.options.Output+".md", - ) + mdFile = openOrCreateFile(r.options.Resume, r.options.Output+".md") defer func() { _ = mdFile.Close() }() } - jsonOrCsv := (r.options.JSONOutput || r.options.CSVOutput || r.options.MarkDownOutput) - jsonAndCsv := (r.options.JSONOutput && r.options.CSVOutput) - if r.options.Output != "" && plainFile == nil && !jsonOrCsv { + jsonOrCsvOrMD := (r.options.JSONOutput || r.options.CSVOutput || r.options.MarkDownOutput) + jsonAndCsvAndMD := (r.options.JSONOutput && r.options.CSVOutput && r.options.MarkDownOutput) + if r.options.Output != "" && plainFile == nil && !jsonOrCsvOrMD { plainFile = openOrCreateFile(r.options.Resume, r.options.Output) defer func() { _ = plainFile.Close() @@ -854,7 +848,7 @@ func (r *Runner) RunEnumeration() { if r.options.Output != "" && r.options.JSONOutput && jsonFile == nil { ext := "" - if jsonAndCsv { + if jsonAndCsvAndMD { ext = ".json" } jsonFile = openOrCreateFile(r.options.Resume, r.options.Output+ext) @@ -865,7 +859,7 @@ func (r *Runner) RunEnumeration() { if r.options.Output != "" && r.options.CSVOutput && csvFile == nil { ext := "" - if jsonAndCsv { + if jsonAndCsvAndMD { ext = ".csv" } csvFile = openOrCreateFile(r.options.Resume, r.options.Output+ext) @@ -874,6 +868,17 @@ func (r *Runner) RunEnumeration() { }() } + if r.options.Output != "" && r.options.MarkDownOutput && mdFile == nil { + ext := "" + if jsonAndCsvAndMD { + ext = ".md" + } + mdFile = openOrCreateFile(r.options.Resume, r.options.Output+ext) + defer func() { + _ = mdFile.Close() + }() + } + if r.options.CSVOutput { outEncoding := strings.ToLower(r.options.CSVOutputEncoding) switch outEncoding { @@ -888,7 +893,7 @@ func (r *Runner) RunEnumeration() { gologger.Fatal().Msgf("unknown csv output encoding: %s\n", r.options.CSVOutputEncoding) } headers := Result{}.CSVHeader() - if !r.options.OutputAll && !jsonAndCsv { + if !r.options.OutputAll && !jsonAndCsvAndMD { gologger.Silent().Msgf("%s\n", headers) } @@ -1103,7 +1108,7 @@ func (r *Runner) RunEnumeration() { } } - if !r.options.DisableStdout && (!jsonOrCsv || jsonAndCsv || r.options.OutputAll) { + if !r.options.DisableStdout && (!jsonOrCsvOrMD || jsonAndCsvAndMD || r.options.OutputAll) { gologger.Silent().Msgf("%s\n", resp.str) } @@ -1216,7 +1221,7 @@ func (r *Runner) RunEnumeration() { if r.options.JSONOutput { row := resp.JSON(&r.scanopts) - if !r.options.OutputAll && !jsonAndCsv { + if !r.options.OutputAll && !jsonAndCsvAndMD { gologger.Silent().Msgf("%s\n", row) } @@ -1229,7 +1234,7 @@ func (r *Runner) RunEnumeration() { if r.options.CSVOutput { row := resp.CSVRow(&r.scanopts) - if !r.options.OutputAll && !jsonAndCsv { + if !r.options.OutputAll && !jsonAndCsvAndMD { gologger.Silent().Msgf("%s\n", row) } @@ -1239,7 +1244,7 @@ func (r *Runner) RunEnumeration() { } } - if r.options.MarkDownOutput { + if r.options.MarkDownOutput || r.options.OutputAll { if !markdownHeaderWritten { header := MarkdownHeader(resp) if !r.options.OutputAll { From 3bcbe1b3b1a0b83e08418c5e2f5bc00da15b08f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Sat, 3 Jan 2026 17:44:30 +0300 Subject: [PATCH 5/7] fix: handle errcheck lint errors --- runner/runner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runner/runner.go b/runner/runner.go index e5c5414f..50cf0a56 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -1251,7 +1251,7 @@ func (r *Runner) RunEnumeration() { gologger.Silent().Msgf("%s", header) } if mdFile != nil { - mdFile.WriteString(header) + _, _ = mdFile.WriteString(header) } markdownHeaderWritten = true } @@ -1262,7 +1262,7 @@ func (r *Runner) RunEnumeration() { gologger.Silent().Msgf("%s", row) } if mdFile != nil { - mdFile.WriteString(row) + _, _ = mdFile.WriteString(row) } } From bad939e139b9e8028d83a89d2f3bf0b2a558a002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Sat, 3 Jan 2026 17:51:16 +0300 Subject: [PATCH 6/7] refactor: make MarkdownHeader a method on Result --- runner/md_output.go | 2 +- runner/runner.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runner/md_output.go b/runner/md_output.go index 3428210e..0eca258d 100644 --- a/runner/md_output.go +++ b/runner/md_output.go @@ -6,7 +6,7 @@ import ( "strings" ) -func MarkdownHeader(r Result) string { +func (r Result) MarkdownHeader() string { //nolint var b strings.Builder b.WriteString("| URL | Status | Method | IP | Size | Words | Lines | Title | CDN |") b.WriteString("\n") diff --git a/runner/runner.go b/runner/runner.go index 50cf0a56..b541bb12 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -1246,7 +1246,7 @@ func (r *Runner) RunEnumeration() { if r.options.MarkDownOutput || r.options.OutputAll { if !markdownHeaderWritten { - header := MarkdownHeader(resp) + header := resp.MarkdownHeader() if !r.options.OutputAll { gologger.Silent().Msgf("%s", header) } From 6399f7808019c7b9172c7342182515f73683e3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Sat, 3 Jan 2026 18:05:13 +0300 Subject: [PATCH 7/7] refactor: dynamic markdown header/row generation from struct tags --- runner/md_output.go | 89 ++++++++++++++++++++-------- runner/types.go | 138 ++++++++++++++++++++++---------------------- 2 files changed, 135 insertions(+), 92 deletions(-) diff --git a/runner/md_output.go b/runner/md_output.go index 0eca258d..6aac73f6 100644 --- a/runner/md_output.go +++ b/runner/md_output.go @@ -2,48 +2,91 @@ package runner import ( "fmt" - "net/http" + "reflect" "strings" ) func (r Result) MarkdownHeader() string { //nolint + var headers []string + + t := reflect.TypeOf(r) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + tag := field.Tag.Get("md") + if tag == "" || tag == "-" { + continue + } + headers = append(headers, tag) + } + var b strings.Builder - b.WriteString("| URL | Status | Method | IP | Size | Words | Lines | Title | CDN |") + b.WriteString("|") + for _, h := range headers { + fmt.Fprintf(&b, " %s |", h) + } b.WriteString("\n") - b.WriteString("|---|---|---|---|---|---|---|---|---|") + + b.WriteString("|") + for range headers { + b.WriteString("---|") + } b.WriteString("\n") return b.String() } -func (r Result) MarkdownRow(scanopts *ScanOptions) string { - var b strings.Builder +func (r Result) MarkdownRow(scanopts *ScanOptions) string { //nolint + var values []string - fmt.Fprintf(&b, "| %s | `%d %s` | `%s` | `%s` | %d | %d | %d |", - escapeMarkdown(r.URL), - r.StatusCode, http.StatusText(r.StatusCode), - r.Method, - r.HostIP, - r.ContentLength, - r.Words, - r.Lines) - - if r.Title != "" { - fmt.Fprintf(&b, " %s |", escapeMarkdown(r.Title)) - } else { - b.WriteString(" |") - } + v := reflect.ValueOf(r) + t := v.Type() - if r.CDNName != "" { - fmt.Fprintf(&b, " `%s` |", r.CDNName) - } else { - b.WriteString(" |") + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + tag := field.Tag.Get("md") + if tag == "" || tag == "-" { + continue + } + + fieldValue := v.Field(i) + values = append(values, formatMarkdownValue(fieldValue)) } + var b strings.Builder + b.WriteString("|") + for _, val := range values { + fmt.Fprintf(&b, " %s |", val) + } b.WriteString("\n") + return b.String() } +func formatMarkdownValue(v reflect.Value) string { + switch v.Kind() { + case reflect.String: + return escapeMarkdown(v.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return fmt.Sprintf("%d", v.Int()) + case reflect.Bool: + return fmt.Sprintf("%t", v.Bool()) + case reflect.Slice: + if v.Len() == 0 { + return "" + } + var items []string + for i := 0; i < v.Len(); i++ { + items = append(items, fmt.Sprintf("%v", v.Index(i).Interface())) + } + return escapeMarkdown(strings.Join(items, ", ")) + default: + if v.CanInterface() { + return escapeMarkdown(fmt.Sprintf("%v", v.Interface())) + } + return "" + } +} + func escapeMarkdown(s string) string { replacer := strings.NewReplacer( "|", "\\|", diff --git a/runner/types.go b/runner/types.go index ab013a70..9acd0e7d 100644 --- a/runner/types.go +++ b/runner/types.go @@ -33,75 +33,75 @@ func (o AsnResponse) String() string { // Result of a scan type Result struct { - Timestamp time.Time `json:"timestamp,omitempty" csv:"timestamp" mapstructure:"timestamp"` - LinkRequest []NetworkRequest `json:"link_request,omitempty" csv:"link_request" mapstructure:"link_request"` - ASN *AsnResponse `json:"asn,omitempty" csv:"-" mapstructure:"asn"` - Err error `json:"-" csv:"-" mapstructure:"-"` - CSPData *httpx.CSPData `json:"csp,omitempty" csv:"-" mapstructure:"csp"` - TLSData *clients.Response `json:"tls,omitempty" csv:"-" mapstructure:"tls"` - Hashes map[string]interface{} `json:"hash,omitempty" csv:"-" mapstructure:"hash"` - ExtractRegex []string `json:"extract_regex,omitempty" csv:"extract_regex" mapstructure:"extract_regex"` - CDNName string `json:"cdn_name,omitempty" csv:"cdn_name" mapstructure:"cdn_name"` - CDNType string `json:"cdn_type,omitempty" csv:"cdn_type" mapstructure:"cdn_type"` - SNI string `json:"sni,omitempty" csv:"sni" mapstructure:"sni"` - Port string `json:"port,omitempty" csv:"port" mapstructure:"port"` - Raw string `json:"-" csv:"-" mapstructure:"-"` - URL string `json:"url,omitempty" csv:"url" mapstructure:"url"` - Input string `json:"input,omitempty" csv:"input" mapstructure:"input"` - Location string `json:"location,omitempty" csv:"location" mapstructure:"location"` - Title string `json:"title,omitempty" csv:"title" mapstructure:"title"` - str string `json:"-" csv:"-" mapstructure:"-"` - Scheme string `json:"scheme,omitempty" csv:"scheme" mapstructure:"scheme"` - Error string `json:"error,omitempty" csv:"error" mapstructure:"error"` - WebServer string `json:"webserver,omitempty" csv:"webserver" mapstructure:"webserver"` - ResponseBody string `json:"body,omitempty" csv:"-" mapstructure:"body"` - BodyPreview string `json:"body_preview,omitempty" csv:"body_preview" mapstructure:"body_preview"` - ContentType string `json:"content_type,omitempty" csv:"content_type" mapstructure:"content_type"` - Method string `json:"method,omitempty" csv:"method" mapstructure:"method"` - Host string `json:"host,omitempty" csv:"host" mapstructure:"host"` - HostIP string `json:"host_ip,omitempty" csv:"host_ip" mapstructure:"host_ip"` - Path string `json:"path,omitempty" csv:"path" mapstructure:"path"` - FavIconMMH3 string `json:"favicon,omitempty" csv:"favicon" mapstructure:"favicon"` - FavIconMD5 string `json:"favicon_md5,omitempty" csv:"favicon_md5" mapstructure:"favicon_md5"` - FaviconPath string `json:"favicon_path,omitempty" csv:"favicon_path" mapstructure:"favicon_path"` - FaviconURL string `json:"favicon_url,omitempty" csv:"favicon_url" mapstructure:"favicon_url"` - FinalURL string `json:"final_url,omitempty" csv:"final_url" mapstructure:"final_url"` - ResponseHeaders map[string]interface{} `json:"header,omitempty" csv:"-" mapstructure:"header"` - RawHeaders string `json:"raw_header,omitempty" csv:"-" mapstructure:"raw_header"` - Request string `json:"request,omitempty" csv:"-" mapstructure:"request"` - ResponseTime string `json:"time,omitempty" csv:"time" mapstructure:"time"` - JarmHash string `json:"jarm_hash,omitempty" csv:"jarm_hash" mapstructure:"jarm_hash"` - ChainStatusCodes []int `json:"chain_status_codes,omitempty" csv:"chain_status_codes" mapstructure:"chain_status_codes"` - A []string `json:"a,omitempty" csv:"a" mapstructure:"a"` - AAAA []string `json:"aaaa,omitempty" csv:"aaaa" mapstructure:"aaaa"` - CNAMEs []string `json:"cname,omitempty" csv:"cname" mapstructure:"cname"` - Technologies []string `json:"tech,omitempty" csv:"tech" mapstructure:"tech"` - Extracts map[string][]string `json:"extracts,omitempty" csv:"-" mapstructure:"extracts"` - Chain []httpx.ChainItem `json:"chain,omitempty" csv:"-" mapstructure:"chain"` - Words int `json:"words" csv:"words" mapstructure:"words"` - Lines int `json:"lines" csv:"lines" mapstructure:"lines"` - StatusCode int `json:"status_code" csv:"status_code" mapstructure:"status_code"` - ContentLength int `json:"content_length" csv:"content_length" mapstructure:"content_length"` - Failed bool `json:"failed" csv:"failed" mapstructure:"failed"` - VHost bool `json:"vhost,omitempty" csv:"vhost" mapstructure:"vhost"` - WebSocket bool `json:"websocket,omitempty" csv:"websocket" mapstructure:"websocket"` - CDN bool `json:"cdn,omitempty" csv:"cdn" mapstructure:"cdn"` - HTTP2 bool `json:"http2,omitempty" csv:"http2" mapstructure:"http2"` - Pipeline bool `json:"pipeline,omitempty" csv:"pipeline" mapstructure:"pipeline"` - HeadlessBody string `json:"headless_body,omitempty" csv:"headless_body" mapstructure:"headless_body"` - ScreenshotBytes []byte `json:"screenshot_bytes,omitempty" csv:"screenshot_bytes" mapstructure:"screenshot_bytes"` - StoredResponsePath string `json:"stored_response_path,omitempty" csv:"stored_response_path" mapstructure:"stored_response_path"` - ScreenshotPath string `json:"screenshot_path,omitempty" csv:"screenshot_path" mapstructure:"screenshot_path"` - ScreenshotPathRel string `json:"screenshot_path_rel,omitempty" csv:"screenshot_path_rel" mapstructure:"screenshot_path_rel"` - KnowledgeBase map[string]interface{} `json:"knowledgebase,omitempty" csv:"-" mapstructure:"knowledgebase"` - Resolvers []string `json:"resolvers,omitempty" csv:"resolvers" mapstructure:"resolvers"` - Fqdns []string `json:"body_fqdn,omitempty" csv:"body_fqdn" mapstructure:"body_fqdn"` - Domains []string `json:"body_domains,omitempty" csv:"body_domains" mapstructure:"body_domains"` - TechnologyDetails map[string]wappalyzer.AppInfo `json:"-" csv:"-" mapstructure:"-"` - RequestRaw []byte `json:"-" csv:"-" mapstructure:"-"` - Response *httpx.Response `json:"-" csv:"-" mapstructure:"-"` - FaviconData []byte `json:"-" csv:"-" mapstructure:"-"` - Trace *retryablehttp.TraceInfo `json:"trace,omitempty" csv:"-" mapstructure:"trace"` + Timestamp time.Time `json:"timestamp,omitempty" csv:"timestamp" md:"timestamp" mapstructure:"timestamp"` + LinkRequest []NetworkRequest `json:"link_request,omitempty" csv:"link_request" md:"link_request" mapstructure:"link_request"` + ASN *AsnResponse `json:"asn,omitempty" csv:"-" md:"-" mapstructure:"asn"` + Err error `json:"-" csv:"-" md:"-" mapstructure:"-"` + CSPData *httpx.CSPData `json:"csp,omitempty" csv:"-" md:"-" mapstructure:"csp"` + TLSData *clients.Response `json:"tls,omitempty" csv:"-" md:"-" mapstructure:"tls"` + Hashes map[string]interface{} `json:"hash,omitempty" csv:"-" md:"-" mapstructure:"hash"` + ExtractRegex []string `json:"extract_regex,omitempty" csv:"extract_regex" md:"extract_regex" mapstructure:"extract_regex"` + CDNName string `json:"cdn_name,omitempty" csv:"cdn_name" md:"cdn_name" mapstructure:"cdn_name"` + CDNType string `json:"cdn_type,omitempty" csv:"cdn_type" md:"cdn_type" mapstructure:"cdn_type"` + SNI string `json:"sni,omitempty" csv:"sni" md:"sni" mapstructure:"sni"` + Port string `json:"port,omitempty" csv:"port" md:"port" mapstructure:"port"` + Raw string `json:"-" csv:"-" md:"-" mapstructure:"-"` + URL string `json:"url,omitempty" csv:"url" md:"url" mapstructure:"url"` + Input string `json:"input,omitempty" csv:"input" md:"input" mapstructure:"input"` + Location string `json:"location,omitempty" csv:"location" md:"location" mapstructure:"location"` + Title string `json:"title,omitempty" csv:"title" md:"title" mapstructure:"title"` + str string `json:"-" csv:"-" md:"-" mapstructure:"-"` + Scheme string `json:"scheme,omitempty" csv:"scheme" md:"scheme" mapstructure:"scheme"` + Error string `json:"error,omitempty" csv:"error" md:"error" mapstructure:"error"` + WebServer string `json:"webserver,omitempty" csv:"webserver" md:"webserver" mapstructure:"webserver"` + ResponseBody string `json:"body,omitempty" csv:"-" md:"-" mapstructure:"body"` + BodyPreview string `json:"body_preview,omitempty" csv:"body_preview" md:"body_preview" mapstructure:"body_preview"` + ContentType string `json:"content_type,omitempty" csv:"content_type" md:"content_type" mapstructure:"content_type"` + Method string `json:"method,omitempty" csv:"method" md:"method" mapstructure:"method"` + Host string `json:"host,omitempty" csv:"host" md:"host" mapstructure:"host"` + HostIP string `json:"host_ip,omitempty" csv:"host_ip" md:"host_ip" mapstructure:"host_ip"` + Path string `json:"path,omitempty" csv:"path" md:"path" mapstructure:"path"` + FavIconMMH3 string `json:"favicon,omitempty" csv:"favicon" md:"favicon" mapstructure:"favicon"` + FavIconMD5 string `json:"favicon_md5,omitempty" csv:"favicon_md5" md:"favicon_md5" mapstructure:"favicon_md5"` + FaviconPath string `json:"favicon_path,omitempty" csv:"favicon_path" md:"favicon_path" mapstructure:"favicon_path"` + FaviconURL string `json:"favicon_url,omitempty" csv:"favicon_url" md:"favicon_url" mapstructure:"favicon_url"` + FinalURL string `json:"final_url,omitempty" csv:"final_url" md:"final_url" mapstructure:"final_url"` + ResponseHeaders map[string]interface{} `json:"header,omitempty" csv:"-" md:"-" mapstructure:"header"` + RawHeaders string `json:"raw_header,omitempty" csv:"-" md:"-" mapstructure:"raw_header"` + Request string `json:"request,omitempty" csv:"-" md:"-" mapstructure:"request"` + ResponseTime string `json:"time,omitempty" csv:"time" md:"time" mapstructure:"time"` + JarmHash string `json:"jarm_hash,omitempty" csv:"jarm_hash" md:"jarm_hash" mapstructure:"jarm_hash"` + ChainStatusCodes []int `json:"chain_status_codes,omitempty" csv:"chain_status_codes" md:"chain_status_codes" mapstructure:"chain_status_codes"` + A []string `json:"a,omitempty" csv:"a" md:"a" mapstructure:"a"` + AAAA []string `json:"aaaa,omitempty" csv:"aaaa" md:"aaaa" mapstructure:"aaaa"` + CNAMEs []string `json:"cname,omitempty" csv:"cname" md:"cname" mapstructure:"cname"` + Technologies []string `json:"tech,omitempty" csv:"tech" md:"tech" mapstructure:"tech"` + Extracts map[string][]string `json:"extracts,omitempty" csv:"-" md:"-" mapstructure:"extracts"` + Chain []httpx.ChainItem `json:"chain,omitempty" csv:"-" md:"-" mapstructure:"chain"` + Words int `json:"words" csv:"words" md:"words" mapstructure:"words"` + Lines int `json:"lines" csv:"lines" md:"lines" mapstructure:"lines"` + StatusCode int `json:"status_code" csv:"status_code" md:"status_code" mapstructure:"status_code"` + ContentLength int `json:"content_length" csv:"content_length" md:"content_length" mapstructure:"content_length"` + Failed bool `json:"failed" csv:"failed" md:"failed" mapstructure:"failed"` + VHost bool `json:"vhost,omitempty" csv:"vhost" md:"vhost" mapstructure:"vhost"` + WebSocket bool `json:"websocket,omitempty" csv:"websocket" md:"websocket" mapstructure:"websocket"` + CDN bool `json:"cdn,omitempty" csv:"cdn" md:"cdn" mapstructure:"cdn"` + HTTP2 bool `json:"http2,omitempty" csv:"http2" md:"http2" mapstructure:"http2"` + Pipeline bool `json:"pipeline,omitempty" csv:"pipeline" md:"pipeline" mapstructure:"pipeline"` + HeadlessBody string `json:"headless_body,omitempty" csv:"headless_body" md:"headless_body" mapstructure:"headless_body"` + ScreenshotBytes []byte `json:"screenshot_bytes,omitempty" csv:"screenshot_bytes" md:"screenshot_bytes" mapstructure:"screenshot_bytes"` + StoredResponsePath string `json:"stored_response_path,omitempty" csv:"stored_response_path" md:"stored_response_path" mapstructure:"stored_response_path"` + ScreenshotPath string `json:"screenshot_path,omitempty" csv:"screenshot_path" md:"screenshot_path" mapstructure:"screenshot_path"` + ScreenshotPathRel string `json:"screenshot_path_rel,omitempty" csv:"screenshot_path_rel" md:"screenshot_path_rel" mapstructure:"screenshot_path_rel"` + KnowledgeBase map[string]interface{} `json:"knowledgebase,omitempty" csv:"-" md:"-" mapstructure:"knowledgebase"` + Resolvers []string `json:"resolvers,omitempty" csv:"resolvers" md:"resolvers" mapstructure:"resolvers"` + Fqdns []string `json:"body_fqdn,omitempty" csv:"body_fqdn" md:"body_fqdn" mapstructure:"body_fqdn"` + Domains []string `json:"body_domains,omitempty" csv:"body_domains" md:"body_domains" mapstructure:"body_domains"` + TechnologyDetails map[string]wappalyzer.AppInfo `json:"-" csv:"-" md:"-" mapstructure:"-"` + RequestRaw []byte `json:"-" csv:"-" md:"-" mapstructure:"-"` + Response *httpx.Response `json:"-" csv:"-" md:"-" mapstructure:"-"` + FaviconData []byte `json:"-" csv:"-" md:"-" mapstructure:"-"` + Trace *retryablehttp.TraceInfo `json:"trace,omitempty" csv:"-" md:"-" mapstructure:"trace"` } type Trace struct {