feat: fix global matchers JSON output and CLI formatting

- Add GlobalTemplateID, GlobalTemplatePath, and GlobalTemplateInfo fields to ResultEvent
- Preserve original template information in JSON output when global matchers trigger
- Store global template information in separate fields for visibility
- Update CLI format for global matchers: [global-template-id:matcher] [global] [original-template-id] [protocol] [severity]
- Use global template severity in CLI output instead of original template severity
- Display original template ID in white (no color) for distinction
- Update both HTTP and Network operators for consistency

Fixes issue where global matcher results showed global template info instead of original template info in JSON output.
This commit is contained in:
Parth 2025-08-06 16:36:41 +05:30
parent 8ef3662634
commit 1abc65dea4
4 changed files with 166 additions and 69 deletions

View File

@ -19,38 +19,63 @@ func (w *StandardWriter) formatScreen(output *ResultEvent) []byte {
builder.WriteString(w.aurora.Cyan(output.Timestamp.Format("2006-01-02 15:04:05")).String())
builder.WriteString("] ")
}
builder.WriteRune('[')
builder.WriteString(w.aurora.BrightGreen(output.TemplateID).String())
if output.MatcherName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.MatcherName).Bold().String())
} else if output.ExtractorName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.ExtractorName).Bold().String())
}
if w.matcherStatus {
builder.WriteString("] [")
if !output.MatcherStatus {
builder.WriteString(w.aurora.Red("failed").String())
} else {
builder.WriteString(w.aurora.Green("matched").String())
}
}
if output.GlobalMatchers {
// For global matchers: [global-template-id:matcher-name] [global] [original-template-id] [http] [severity]
builder.WriteRune('[')
builder.WriteString(w.aurora.BrightGreen(output.GlobalTemplateID).String())
if output.MatcherName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.MatcherName).Bold().String())
} else if output.ExtractorName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.ExtractorName).Bold().String())
}
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightMagenta("global").String())
builder.WriteString("] [")
builder.WriteString(output.TemplateID) // Original template ID in white (no color)
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightBlue(output.Type).String())
builder.WriteString("] ")
builder.WriteString("[")
// Use global template severity instead of original template severity
builder.WriteString(w.severityColors(output.GlobalTemplateInfo.SeverityHolder.Severity))
builder.WriteString("] ")
} else {
// For regular templates: [template-id:matcher-name] [http] [severity]
builder.WriteRune('[')
builder.WriteString(w.aurora.BrightGreen(output.TemplateID).String())
if output.MatcherName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.MatcherName).Bold().String())
} else if output.ExtractorName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.ExtractorName).Bold().String())
}
if w.matcherStatus {
builder.WriteString("] [")
if !output.MatcherStatus {
builder.WriteString(w.aurora.Red("failed").String())
} else {
builder.WriteString(w.aurora.Green("matched").String())
}
}
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightBlue(output.Type).String())
builder.WriteString("] ")
builder.WriteString("[")
builder.WriteString(w.severityColors(output.Info.SeverityHolder.Severity))
builder.WriteString("] ")
}
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightBlue(output.Type).String())
builder.WriteString("] ")
builder.WriteString("[")
builder.WriteString(w.severityColors(output.Info.SeverityHolder.Severity))
builder.WriteString("] ")
}
if output.Matched != "" {
builder.WriteString(output.Matched)

View File

@ -199,6 +199,12 @@ type ResultEvent struct {
// GlobalMatchers identifies whether the matches was detected in the response
// of another template's result event
GlobalMatchers bool `json:"global-matchers,omitempty"`
// GlobalTemplateID is the ID of the global template that matched (only set when GlobalMatchers is true)
GlobalTemplateID string `json:"global-template-id,omitempty"`
// GlobalTemplatePath is the path of the global template that matched (only set when GlobalMatchers is true)
GlobalTemplatePath string `json:"global-template-path,omitempty"`
// GlobalTemplateInfo contains information block of the global template (only set when GlobalMatchers is true)
GlobalTemplateInfo model.Info `json:"global-template-info,omitempty"`
// IssueTrackers is the metadata for issue trackers
IssueTrackers map[string]IssueTrackerMetadata `json:"issue_trackers,omitempty"`

View File

@ -173,30 +173,60 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
if value, ok := wrapped.InternalEvent["analyzer_details"]; ok {
analyzerDetails = value.(string)
}
// For global matchers, use original template info and store global template info separately
var templateID, templatePath string
var templateInfo model.Info
var globalTemplateID, globalTemplatePath string
var globalTemplateInfo model.Info
if isGlobalMatchers {
// Use original template information
templateID = types.ToString(wrapped.InternalEvent["origin-template-id"])
templatePath = types.ToString(wrapped.InternalEvent["origin-template-path"])
if originInfo := wrapped.InternalEvent["origin-template-info"]; originInfo != nil {
templateInfo = originInfo.(model.Info)
}
// Store global template information
globalTemplateID = types.ToString(wrapped.InternalEvent["template-id"])
globalTemplatePath = types.ToString(wrapped.InternalEvent["template-path"])
if globalInfo := wrapped.InternalEvent["template-info"]; globalInfo != nil {
globalTemplateInfo = globalInfo.(model.Info)
}
} else {
// Use current template information for non-global matchers
templateID = types.ToString(wrapped.InternalEvent["template-id"])
templatePath = types.ToString(wrapped.InternalEvent["template-path"])
templateInfo = wrapped.InternalEvent["template-info"].(model.Info)
}
data := &output.ResultEvent{
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
Info: wrapped.InternalEvent["template-info"].(model.Info),
TemplateVerifier: request.options.TemplateVerifier,
Type: types.ToString(wrapped.InternalEvent["type"]),
Host: fields.Host,
Port: fields.Port,
Scheme: fields.Scheme,
URL: fields.URL,
Path: fields.Path,
Matched: types.ToString(wrapped.InternalEvent["matched"]),
Metadata: wrapped.OperatorsResult.PayloadValues,
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
Timestamp: time.Now(),
MatcherStatus: true,
IP: fields.Ip,
GlobalMatchers: isGlobalMatchers,
Request: types.ToString(wrapped.InternalEvent["request"]),
Response: request.truncateResponse(wrapped.InternalEvent["response"]),
CURLCommand: types.ToString(wrapped.InternalEvent["curl-command"]),
TemplateEncoded: request.options.EncodeTemplate(),
Error: types.ToString(wrapped.InternalEvent["error"]),
AnalyzerDetails: analyzerDetails,
TemplateID: templateID,
TemplatePath: templatePath,
Info: templateInfo,
TemplateVerifier: request.options.TemplateVerifier,
Type: types.ToString(wrapped.InternalEvent["type"]),
Host: fields.Host,
Port: fields.Port,
Scheme: fields.Scheme,
URL: fields.URL,
Path: fields.Path,
Matched: types.ToString(wrapped.InternalEvent["matched"]),
Metadata: wrapped.OperatorsResult.PayloadValues,
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
Timestamp: time.Now(),
MatcherStatus: true,
IP: fields.Ip,
GlobalMatchers: isGlobalMatchers,
GlobalTemplateID: globalTemplateID,
GlobalTemplatePath: globalTemplatePath,
GlobalTemplateInfo: globalTemplateInfo,
Request: types.ToString(wrapped.InternalEvent["request"]),
Response: request.truncateResponse(wrapped.InternalEvent["response"]),
CURLCommand: types.ToString(wrapped.InternalEvent["curl-command"]),
TemplateEncoded: request.options.EncodeTemplate(),
Error: types.ToString(wrapped.InternalEvent["error"]),
AnalyzerDetails: analyzerDetails,
}
return data
}

View File

@ -99,25 +99,61 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
if types.ToString(wrapped.InternalEvent["ip"]) != "" {
fields.Ip = types.ToString(wrapped.InternalEvent["ip"])
}
var isGlobalMatchers bool
if value, ok := wrapped.InternalEvent["global-matchers"]; ok {
isGlobalMatchers = value.(bool)
}
// For global matchers, use original template info and store global template info separately
var templateID, templatePath string
var templateInfo model.Info
var globalTemplateID, globalTemplatePath string
var globalTemplateInfo model.Info
if isGlobalMatchers {
// Use original template information
templateID = types.ToString(wrapped.InternalEvent["origin-template-id"])
templatePath = types.ToString(wrapped.InternalEvent["origin-template-path"])
if originInfo := wrapped.InternalEvent["origin-template-info"]; originInfo != nil {
templateInfo = originInfo.(model.Info)
}
// Store global template information
globalTemplateID = types.ToString(wrapped.InternalEvent["template-id"])
globalTemplatePath = types.ToString(wrapped.InternalEvent["template-path"])
if globalInfo := wrapped.InternalEvent["template-info"]; globalInfo != nil {
globalTemplateInfo = globalInfo.(model.Info)
}
} else {
// Use current template information for non-global matchers
templateID = types.ToString(wrapped.InternalEvent["template-id"])
templatePath = types.ToString(wrapped.InternalEvent["template-path"])
templateInfo = wrapped.InternalEvent["template-info"].(model.Info)
}
data := &output.ResultEvent{
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
Info: wrapped.InternalEvent["template-info"].(model.Info),
TemplateVerifier: request.options.TemplateVerifier,
Type: types.ToString(wrapped.InternalEvent["type"]),
Host: fields.Host,
Port: fields.Port,
URL: fields.URL,
Matched: types.ToString(wrapped.InternalEvent["matched"]),
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
Metadata: wrapped.OperatorsResult.PayloadValues,
Timestamp: time.Now(),
MatcherStatus: true,
IP: fields.Ip,
Request: types.ToString(wrapped.InternalEvent["request"]),
Response: types.ToString(wrapped.InternalEvent["data"]),
TemplateEncoded: request.options.EncodeTemplate(),
Error: types.ToString(wrapped.InternalEvent["error"]),
TemplateID: templateID,
TemplatePath: templatePath,
Info: templateInfo,
TemplateVerifier: request.options.TemplateVerifier,
Type: types.ToString(wrapped.InternalEvent["type"]),
Host: fields.Host,
Port: fields.Port,
URL: fields.URL,
Matched: types.ToString(wrapped.InternalEvent["matched"]),
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
Metadata: wrapped.OperatorsResult.PayloadValues,
Timestamp: time.Now(),
MatcherStatus: true,
IP: fields.Ip,
GlobalMatchers: isGlobalMatchers,
GlobalTemplateID: globalTemplateID,
GlobalTemplatePath: globalTemplatePath,
GlobalTemplateInfo: globalTemplateInfo,
Request: types.ToString(wrapped.InternalEvent["request"]),
Response: types.ToString(wrapped.InternalEvent["data"]),
TemplateEncoded: request.options.EncodeTemplate(),
Error: types.ToString(wrapped.InternalEvent["error"]),
}
return data
}