mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 21:55:26 +00:00
feat(tmplexec): add feature flag to control error enrichment in debug mode for better traceability and cleaner output
This commit is contained in:
parent
3e9bee7400
commit
1af32d3b9d
@ -217,7 +217,7 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
|
|||||||
lastMatcherEvent.Lock()
|
lastMatcherEvent.Lock()
|
||||||
defer lastMatcherEvent.Unlock()
|
defer lastMatcherEvent.Unlock()
|
||||||
|
|
||||||
lastMatcherEvent.InternalEvent["error"] = getErrorCause(ctx.GenerateErrorMessage())
|
lastMatcherEvent.InternalEvent["error"] = getErrorCause(ctx.GenerateErrorMessage(), e.options.Options.Debug)
|
||||||
|
|
||||||
writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus)
|
writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus)
|
||||||
}
|
}
|
||||||
@ -233,7 +233,7 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
|
|||||||
Info: e.options.TemplateInfo,
|
Info: e.options.TemplateInfo,
|
||||||
Type: e.getTemplateType(),
|
Type: e.getTemplateType(),
|
||||||
Host: ctx.Input.MetaInput.Input,
|
Host: ctx.Input.MetaInput.Input,
|
||||||
Error: getErrorCause(ctx.GenerateErrorMessage()),
|
Error: getErrorCause(ctx.GenerateErrorMessage(), e.options.Options.Debug),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OperatorsResult: &operators.Result{
|
OperatorsResult: &operators.Result{
|
||||||
@ -249,24 +249,30 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
|
|||||||
// getErrorCause tries to parse the cause of given error
|
// getErrorCause tries to parse the cause of given error
|
||||||
// this is legacy support due to use of errorutil in existing libraries
|
// this is legacy support due to use of errorutil in existing libraries
|
||||||
// but this should not be required once all libraries are updated
|
// but this should not be required once all libraries are updated
|
||||||
func getErrorCause(err error) string {
|
// debugMode controls whether to enrich errors with stack traces
|
||||||
|
func getErrorCause(err error, debugMode bool) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
errx := errkit.FromError(err)
|
|
||||||
var cause error
|
if debugMode {
|
||||||
for _, e := range errx.Errors() {
|
errx := errkit.FromError(err)
|
||||||
if e != nil && strings.Contains(e.Error(), "context deadline exceeded") {
|
var cause error
|
||||||
continue
|
for _, e := range errx.Errors() {
|
||||||
|
if e != nil && strings.Contains(e.Error(), "context deadline exceeded") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cause = e
|
||||||
|
break
|
||||||
}
|
}
|
||||||
cause = e
|
if cause == nil {
|
||||||
break
|
cause = errkit.Append(errkit.New("could not get error cause"), errx)
|
||||||
|
}
|
||||||
|
// parseScanErrorWithDebug prettifies the error message and removes everything except the cause
|
||||||
|
return parseScanErrorWithDebug(cause.Error(), debugMode)
|
||||||
}
|
}
|
||||||
if cause == nil {
|
|
||||||
cause = errkit.Append(errkit.New("could not get error cause"), errx)
|
return parseScanErrorWithDebug(err.Error(), debugMode)
|
||||||
}
|
|
||||||
// parseScanError prettifies the error message and removes everything except the cause
|
|
||||||
return parseScanError(cause.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||||
|
|||||||
184
pkg/tmplexec/exec_test.go
Normal file
184
pkg/tmplexec/exec_test.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package tmplexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetErrorCause_DebugMode(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
debugMode bool
|
||||||
|
expectTrace bool
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Debug mode enabled - should enrich error",
|
||||||
|
err: errors.New("malformed request specified: GET /file=>"),
|
||||||
|
debugMode: true,
|
||||||
|
expectTrace: true,
|
||||||
|
description: "When debug mode is enabled, errors should be enriched with additional context",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Debug mode disabled - should not enrich error",
|
||||||
|
err: errors.New("malformed request specified: GET /file=>"),
|
||||||
|
debugMode: false,
|
||||||
|
expectTrace: false,
|
||||||
|
description: "When debug mode is disabled, errors should not be enriched to avoid stack traces",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nil error - debug mode enabled",
|
||||||
|
err: nil,
|
||||||
|
debugMode: true,
|
||||||
|
expectTrace: false,
|
||||||
|
description: "Nil errors should return empty string regardless of debug mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nil error - debug mode disabled",
|
||||||
|
err: nil,
|
||||||
|
debugMode: false,
|
||||||
|
expectTrace: false,
|
||||||
|
description: "Nil errors should return empty string regardless of debug mode",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := getErrorCause(tc.err, tc.debugMode)
|
||||||
|
|
||||||
|
if tc.err == nil {
|
||||||
|
assert.Empty(t, result, "Expected empty string for nil error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic validation - result should contain the original error message
|
||||||
|
assert.Contains(t, result, "malformed request specified",
|
||||||
|
"Result should contain the original error message")
|
||||||
|
|
||||||
|
if tc.debugMode {
|
||||||
|
// In debug mode, we expect the error to be processed through errkit
|
||||||
|
// This doesn't necessarily mean stack traces in the final result,
|
||||||
|
// but ensures the error went through the enrichment path
|
||||||
|
assert.NotEmpty(t, result, "Debug mode should produce non-empty result")
|
||||||
|
} else {
|
||||||
|
// In non-debug mode, we should get a simple error message
|
||||||
|
// without any stack trace information
|
||||||
|
assert.NotContains(t, result, "Stacktrace:",
|
||||||
|
"Non-debug mode should not contain stack trace")
|
||||||
|
assert.NotContains(t, result, "goroutine",
|
||||||
|
"Non-debug mode should not contain goroutine information")
|
||||||
|
assert.NotContains(t, result, "runtime/debug.Stack()",
|
||||||
|
"Non-debug mode should not contain runtime stack information")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseScanErrorWithDebug(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
msg string
|
||||||
|
debugMode bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Simple error - debug mode off",
|
||||||
|
msg: "connection refused",
|
||||||
|
debugMode: false,
|
||||||
|
expected: "connection refused",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Simple error - debug mode on",
|
||||||
|
msg: "connection refused",
|
||||||
|
debugMode: true,
|
||||||
|
expected: "connection refused",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ReadStatusLine error - debug mode off",
|
||||||
|
msg: "ReadStatusLine: malformed HTTP response",
|
||||||
|
debugMode: false,
|
||||||
|
expected: "malformed HTTP response",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty message",
|
||||||
|
msg: "",
|
||||||
|
debugMode: false,
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := parseScanErrorWithDebug(tc.msg, tc.debugMode)
|
||||||
|
assert.Contains(t, result, tc.expected,
|
||||||
|
"Result should contain expected error message")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetErrorCause_ContextDeadlineHandling(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
debugMode bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Context deadline exceeded - debug mode",
|
||||||
|
err: errors.New("context deadline exceeded"),
|
||||||
|
debugMode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Context deadline exceeded - non-debug mode",
|
||||||
|
err: errors.New("context deadline exceeded"),
|
||||||
|
debugMode: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := getErrorCause(tc.err, tc.debugMode)
|
||||||
|
assert.NotEmpty(t, result, "Should handle context deadline exceeded errors")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetErrorCause_ErrorMessageFormat(t *testing.T) {
|
||||||
|
originalError := errors.New("test error message")
|
||||||
|
|
||||||
|
debugResult := getErrorCause(originalError, true)
|
||||||
|
nonDebugResult := getErrorCause(originalError, false)
|
||||||
|
|
||||||
|
// Both should contain the original error message
|
||||||
|
assert.Contains(t, debugResult, "test error message",
|
||||||
|
"Debug result should contain original error message")
|
||||||
|
assert.Contains(t, nonDebugResult, "test error message",
|
||||||
|
"Non-debug result should contain original error message")
|
||||||
|
|
||||||
|
// Non-debug should be simpler (no enrichment artifacts)
|
||||||
|
assert.True(t, len(nonDebugResult) <= len(debugResult) ||
|
||||||
|
strings.Count(nonDebugResult, "\n") <= strings.Count(debugResult, "\n"),
|
||||||
|
"Non-debug result should be simpler than debug result")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark to ensure the non-debug path is more efficient
|
||||||
|
func BenchmarkGetErrorCause_Debug(b *testing.B) {
|
||||||
|
err := errors.New("benchmark test error")
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
getErrorCause(err, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetErrorCause_NonDebug(b *testing.B) {
|
||||||
|
err := errors.New("benchmark test error")
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
getErrorCause(err, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,9 +42,8 @@ var (
|
|||||||
reNoKind = regexp.MustCompile(`([\[][^][]+[\]]|errKind=[^ ]+) `)
|
reNoKind = regexp.MustCompile(`([\[][^][]+[\]]|errKind=[^ ]+) `)
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseScanError parses given scan error and only returning the cause
|
// parseScanErrorWithDebug processes error messages with optional debug mode
|
||||||
// instead of inefficient one
|
func parseScanErrorWithDebug(msg string, debugMode bool) string {
|
||||||
func parseScanError(msg string) string {
|
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -58,14 +57,19 @@ func parseScanError(msg string) string {
|
|||||||
parts := strings.Split(msg, ":")
|
parts := strings.Split(msg, ":")
|
||||||
msg = strings.TrimSpace(parts[len(parts)-1])
|
msg = strings.TrimSpace(parts[len(parts)-1])
|
||||||
}
|
}
|
||||||
e := errkit.FromError(errors.New(msg))
|
|
||||||
for _, err := range e.Errors() {
|
if debugMode {
|
||||||
if err != nil && strings.Contains(err.Error(), "context deadline exceeded") {
|
e := errkit.FromError(errors.New(msg))
|
||||||
continue
|
for _, err := range e.Errors() {
|
||||||
|
if err != nil && strings.Contains(err.Error(), "context deadline exceeded") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg = reNoKind.ReplaceAllString(err.Error(), "")
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
msg = reNoKind.ReplaceAllString(err.Error(), "")
|
wrapped := errkit.Append(errkit.New("failed to get error cause"), e).Error()
|
||||||
return msg
|
return reNoKind.ReplaceAllString(wrapped, "")
|
||||||
}
|
}
|
||||||
wrapped := errkit.Append(errkit.New("failed to get error cause"), e).Error()
|
|
||||||
return reNoKind.ReplaceAllString(wrapped, "")
|
return reNoKind.ReplaceAllString(msg, "")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user