Repository: projectdiscovery/nuclei

Issue:
[FEATURE] Evaluate variables on keys-regex (and keys) in fuzzing
### Is there an existing issue for this?

- [x] I have searched the existing issues.

### Current Behavior

Hello,

When you put a variable like this : 
```
variables:
    included_param: "param1"
```

and try to replace it into 
```
    fuzzing: 
      - parts: [query] 
        type: replace-regex
        replace-regex: "(.*)"
        mode: single
        fuzz: ["{{injection}}"]
        keys-regex:
          - "${{included_param}}"
```

It does not put an error, but does not replace the included_param (works with string but not variable) 

The only output when i put this variable is ` fuzz: rule not applicable : rule not applicable : no component matched on this rule`

### Expected Behavior

variable is replaced into template

### Steps To Reproduce

see above

### Relevant log output

```shell

```

### Environment

```markdown
[INF] Current nuclei version: v3.4.7 (latest)
[INF] Current nuclei-templates version: v10.2.5 (latest)
```

### Anything else?

_No response_

Discussion:

Solution patch:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index dce907af..f8297c10 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -24,7 +24,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - uses: projectdiscovery/actions/setup/go@v1
-      - uses: projectdiscovery/actions/golangci-lint@v1
+      - uses: projectdiscovery/actions/golangci-lint/v2@v1
 
   tests:
     name: "Tests"
diff --git a/Makefile b/Makefile
index 0c3ab083..1b089e76 100644
--- a/Makefile
+++ b/Makefile
@@ -146,5 +146,14 @@ dsl-docs:
 template-validate: build
 template-validate:
 	./bin/nuclei -ut
-	./bin/nuclei -validate -et http/technologies
-	./bin/nuclei -validate -w workflows -et http/technologies
\ No newline at end of file
+	./bin/nuclei -validate \
+		-et .github/ \
+		-et helpers/payloads/ \
+		-et http/technologies \
+		-ept code
+	./bin/nuclei -validate \
+		-w workflows \
+		-et .github/ \
+		-et helpers/payloads/ \
+		-et http/technologies \
+		-ept code
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 40050393..71ab9060 100644
--- a/go.mod
+++ b/go.mod
@@ -191,7 +191,7 @@ require (
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dlclark/regexp2 v1.11.5 // indirect
 	github.com/docker/cli v27.4.1+incompatible // indirect
-	github.com/docker/docker v27.1.1+incompatible // indirect
+	github.com/docker/docker v28.0.0+incompatible // indirect
 	github.com/docker/go-connections v0.5.0 // indirect
 	github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
@@ -213,7 +213,7 @@ require (
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
-	github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
+	github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
diff --git a/go.sum b/go.sum
index 83ee118a..a8e3dcb9 100644
--- a/go.sum
+++ b/go.sum
@@ -297,8 +297,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
 github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
 github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
-github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
+github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
 github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -397,8 +397,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
 github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
-github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
-github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
+github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=
 github.com/goburrow/cache v0.1.4/go.mod h1:cDFesZDnIlrHoNlMYqqMpCRawuXulgx+y7mXU8HZ+/c=
 github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
diff --git a/internal/runner/options.go b/internal/runner/options.go
index bd6b92bc..48d26e9f 100644
--- a/internal/runner/options.go
+++ b/internal/runner/options.go
@@ -39,6 +39,8 @@ const (
 	DefaultDumpTrafficOutputFolder = "output"
 )
 
+var validateOptions = validator.New()
+
 func ConfigureOptions() error {
 	// with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions
 	// if file has the extension `.yaml` or `.json` we consider those as strings and not files to be read
@@ -138,8 +140,7 @@ func ParseOptions(options *types.Options) {
 
 // validateOptions validates the configuration options passed
 func ValidateOptions(options *types.Options) error {
-	validate := validator.New()
-	if err := validate.Struct(options); err != nil {
+	if err := validateOptions.Struct(options); err != nil {
 		if _, ok := err.(*validator.InvalidValidationError); ok {
 			return err
 		}
diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go
index 15d8e9da..a9de026d 100644
--- a/pkg/catalog/config/constants.go
+++ b/pkg/catalog/config/constants.go
@@ -31,7 +31,7 @@ const (
 	CLIConfigFileName               = "config.yaml"
 	ReportingConfigFilename         = "reporting-config.yaml"
 	// Version is the current version of nuclei
-	Version = `v3.4.7`
+	Version = `v3.4.8`
 	// Directory Names of custom templates
 	CustomS3TemplatesDirName     = "s3"
 	CustomGitHubTemplatesDirName = "github"
@@ -46,18 +46,21 @@ const (
 // if the current version is outdated
 func IsOutdatedVersion(current, latest string) bool {
 	if latest == "" {
-		// if pdtm api call failed it's assumed that the current version is outdated
-		// and it will be confirmed while updating from GitHub
-		// this fixes `version string empty` errors
-		return true
+		// NOTE(dwisiswant0): if PDTM API call failed or returned empty, we
+		// cannot determine if templates are outdated w/o additional checks
+		// return false to avoid unnecessary updates.
+		return false
 	}
+
 	current = trimDevIfExists(current)
 	currentVer, _ := semver.NewVersion(current)
 	newVer, _ := semver.NewVersion(latest)
+
 	if currentVer == nil || newVer == nil {
-		// fallback to naive comparison
-		return current == latest
+		// fallback to naive comparison - return true only if they are different
+		return current != latest
 	}
+
 	return newVer.GreaterThan(currentVer)
 }
 
diff --git a/pkg/core/executors.go b/pkg/core/executors.go
index e83aed57..8abcc9d7 100644
--- a/pkg/core/executors.go
+++ b/pkg/core/executors.go
@@ -4,8 +4,10 @@ import (
 	"context"
 	"sync"
 	"sync/atomic"
+	"time"
 
 	"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
+	"github.com/projectdiscovery/nuclei/v3/pkg/output"
 	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
 	"github.com/projectdiscovery/nuclei/v3/pkg/scan"
 	"github.com/projectdiscovery/nuclei/v3/pkg/templates"
@@ -107,6 +109,22 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ
 
 		// Skip if the host has had errors
 		if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, scannedValue)) {
+			skipEvent := &output.ResultEvent{
+				TemplateID:    template.ID,
+				TemplatePath:  template.Path,
+				Info:          template.Info,
+				Type:          e.executerOpts.ProtocolType.String(),
+				Host:          scannedValue.Input,
+				MatcherStatus: false,
+				Error:         "host was skipped as it was found unresponsive",
+				Timestamp:     time.Now(),
+			}
+
+			if e.Callback != nil {
+				e.Callback(skipEvent)
+			} else if e.executerOpts.Output != nil {
+				_ = e.executerOpts.Output.Write(skipEvent)
+			}
 			return true
 		}
 
diff --git a/pkg/fuzz/execute.go b/pkg/fuzz/execute.go
index 8da89d0e..eda78f4d 100644
--- a/pkg/fuzz/execute.go
+++ b/pkg/fuzz/execute.go
@@ -14,6 +14,7 @@ import (
 	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
 	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
 	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
+	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker"
 	"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
 	"github.com/projectdiscovery/retryablehttp-go"
 	errorutil "github.com/projectdiscovery/utils/errors"
@@ -23,7 +24,7 @@ import (
 )
 
 var (
-	ErrRuleNotApplicable = errorutil.NewWithFmt("rule not applicable : %v")
+	ErrRuleNotApplicable = errorutil.NewWithFmt("rule not applicable: %v")
 )
 
 // IsErrRuleNotApplicable checks if an error is due to rule not applicable
@@ -189,6 +190,33 @@ mainLoop:
 	return nil
 }
 
+// evaluateVars evaluates variables in a string using available executor options
+func (rule *Rule) evaluateVars(input string) (string, error) {
+	if rule.options == nil {
+		return input, nil
+	}
+
+	data := generators.MergeMaps(
+		rule.options.Variables.GetAll(),
+		rule.options.Constants,
+		rule.options.Options.Vars.AsMap(),
+	)
+
+	exprs := expressions.FindExpressions(input, marker.ParenthesisOpen, marker.ParenthesisClose, data)
+
+	err := expressions.ContainsUnresolvedVariables(exprs...)
+	if err != nil {
+		return input, err
+	}
+
+	eval, err := expressions.Evaluate(input, data)
+	if err != nil {
+		return input, err
+	}
+
+	return eval, nil
+}
+
 // evaluateVarsWithInteractsh evaluates the variables with Interactsh URLs and updates them accordingly.
 func (rule *Rule) evaluateVarsWithInteractsh(data map[string]interface{}, interactshUrls []string) (map[string]interface{}, []string) {
 	// Check if Interactsh options are configured
@@ -341,23 +369,47 @@ func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *proto
 	if len(rule.Keys) > 0 {
 		rule.keysMap = make(map[string]struct{})
 	}
+
+	// eval vars in "keys"
 	for _, key := range rule.Keys {
-		rule.keysMap[strings.ToLower(key)] = struct{}{}
+		evaluatedKey, err := rule.evaluateVars(key)
+		if err != nil {
+			return errors.Wrap(err, "could not evaluate key")
+		}
+
+		rule.keysMap[strings.ToLower(evaluatedKey)] = struct{}{}
 	}
+
+	// eval vars in "values"
 	for _, value := range rule.ValuesRegex {
-		compiled, err := regexp.Compile(value)
+		evaluatedValue, err := rule.evaluateVars(value)
+		if err != nil {
+			return errors.Wrap(err, "could not evaluate value regex")
+		}
+
+		compiled, err := regexp.Compile(evaluatedValue)
 		if err != nil {
 			return errors.Wrap(err, "could not compile value regex")
 		}
+
 		rule.valuesRegex = append(rule.valuesRegex, compiled)
 	}
+
+	// eval vars in "keys-regex"
 	for _, value := range rule.KeysRegex {
-		compiled, err := regexp.Compile(value)
+		evaluatedValue, err := rule.evaluateVars(value)
+		if err != nil {
+			return errors.Wrap(err, "could not evaluate key regex")
+		}
+
+		compiled, err := regexp.Compile(evaluatedValue)
 		if err != nil {
 			return errors.Wrap(err, "could not compile key regex")
 		}
+
 		rule.keysRegex = append(rule.keysRegex, compiled)
 	}
+
 	if rule.ruleType != replaceRegexRuleType {
 		if rule.ReplaceRegex != "" {
 			return errors.Errorf("replace-regex is only applicable for replace and replace-regex rule types")
@@ -366,11 +418,19 @@ func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *proto
 		if rule.ReplaceRegex == "" {
 			return errors.Errorf("replace-regex is required for replace-regex rule type")
 		}
-		compiled, err := regexp.Compile(rule.ReplaceRegex)
+
+		evalReplaceRegex, err := rule.evaluateVars(rule.ReplaceRegex)
+		if err != nil {
+			return errors.Wrap(err, "could not evaluate replace regex")
+		}
+
+		compiled, err := regexp.Compile(evalReplaceRegex)
 		if err != nil {
 			return errors.Wrap(err, "could not compile replace regex")
 		}
+
 		rule.replaceRegex = compiled
 	}
+
 	return nil
 }
diff --git a/pkg/installer/template.go b/pkg/installer/template.go
index 9e56f12a..d9f3d5ae 100644
--- a/pkg/installer/template.go
+++ b/pkg/installer/template.go
@@ -94,7 +94,24 @@ func (t *TemplateManager) UpdateIfOutdated() error {
 	if !fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {
 		return t.FreshInstallIfNotExists()
 	}
-	if config.DefaultConfig.NeedsTemplateUpdate() {
+
+	needsUpdate := config.DefaultConfig.NeedsTemplateUpdate()
+
+	// NOTE(dwisiswant0): if PDTM API data is not available
+	// (LatestNucleiTemplatesVersion is empty) but we have a current template
+	// version, so we MUST verify against GitHub directly.
+	if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" {
+		ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
+		if err == nil {
+			latestVersion := ghrd.Latest.GetTagName()
+			if config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
+				needsUpdate = true
+				gologger.Debug().Msgf("PDTM API unavailable, verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
+			}
+		}
+	}
+
+	if needsUpdate {
 		return t.updateTemplatesAt(config.DefaultConfig.TemplatesDirectory)
 	}
 	return nil
@@ -142,7 +159,14 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error {
 		return errorutil.NewWithErr(err).Msgf("failed to install templates at %s", dir)
 	}
 
-	gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", config.DefaultConfig.TemplateVersion, ghrd.Latest.GetTagName())
+	latestVersion := ghrd.Latest.GetTagName()
+	currentVersion := config.DefaultConfig.TemplateVersion
+
+	if config.IsOutdatedVersion(currentVersion, latestVersion) {
+		gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", currentVersion, latestVersion)
+	} else {
+		gologger.Debug().Msgf("Updating nuclei-templates from %s to %s (forced update)\n", currentVersion, latestVersion)
+	}
 
 	// write templates to disk
 	if err := t.writeTemplatesToDisk(ghrd, dir); err != nil {
diff --git a/pkg/operators/matchers/validate.go b/pkg/operators/matchers/validate.go
index 0f6a5b91..7dd5a138 100644
--- a/pkg/operators/matchers/validate.go
+++ b/pkg/operators/matchers/validate.go
@@ -8,22 +8,29 @@ import (
 
 	"github.com/antchfx/xpath"
 	sliceutil "github.com/projectdiscovery/utils/slice"
-	"gopkg.in/yaml.v3"
 )
 
 var commonExpectedFields = []string{"Type", "Condition", "Name", "MatchAll", "Negative", "Internal"}
 
 // Validate perform initial validation on the matcher structure
 func (matcher *Matcher) Validate() error {
-	// uses yaml marshaling to convert the struct to map[string]interface to have same field names
+	// Build a map of YAML‐tag names that are actually set (non-zero) in the matcher.
 	matcherMap := make(map[string]interface{})
-	marshaledMatcher, err := yaml.Marshal(matcher)
-	if err != nil {
-		return err
-	}
-	if err := yaml.Unmarshal(marshaledMatcher, &matcherMap); err != nil {
-		return err
+	val := reflect.ValueOf(*matcher)
+	typ := reflect.TypeOf(*matcher)
+	for i := 0; i < typ.NumField(); i++ {
+		field := typ.Field(i)
+		// skip internal / unexported or opt-out fields
+		yamlTag := strings.Split(field.Tag.Get("yaml"), ",")[0]
+		if yamlTag == "" || yamlTag == "-" {
+			continue
+		}
+		if val.Field(i).IsZero() {
+			continue
+		}
+		matcherMap[yamlTag] = struct{}{}
 	}
+	var err error
 
 	var expectedFields []string
 	switch matcher.matcherType {
diff --git a/pkg/output/stats/waf/waf.go b/pkg/output/stats/waf/waf.go
index a660ff7b..7abe68ca 100644
--- a/pkg/output/stats/waf/waf.go
+++ b/pkg/output/stats/waf/waf.go
@@ -53,8 +53,12 @@ func NewWafDetector() *WafDetector {
 }
 
 func (d *WafDetector) DetectWAF(content string) (string, bool) {
+	if d == nil || d.regexCache == nil {
+		return "", false
+	}
+
 	for id, regex := range d.regexCache {
-		if regex.MatchString(content) {
+		if regex != nil && regex.MatchString(content) {
 			return id, true
 		}
 	}
diff --git a/pkg/protocols/headless/engine/engine.go b/pkg/protocols/headless/engine/engine.go
index 63dbb41e..c045580d 100644
--- a/pkg/protocols/headless/engine/engine.go
+++ b/pkg/protocols/headless/engine/engine.go
@@ -20,12 +20,13 @@ import (
 
 // Browser is a browser structure for nuclei headless module
 type Browser struct {
-	customAgent  string
-	tempDir      string
-	previousPIDs map[int32]struct{} // track already running PIDs
-	engine       *rod.Browser
-	options      *types.Options
-	launcher     *launcher.Launcher
+	customAgent    string
+	defaultHeaders map[string]string
+	tempDir        string
+	previousPIDs   map[int32]struct{} // track already running PIDs
+	engine         *rod.Browser
+	options        *types.Options
+	launcher       *launcher.Launcher
 
 	// use getHTTPClient to get the http client
 	httpClient     *http.Client
@@ -95,6 +96,7 @@ func New(options *types.Options) (*Browser, error) {
 	if browserErr := browser.Connect(); browserErr != nil {
 		return nil, browserErr
 	}
+	defaultHeaders := make(map[string]string)
 	customAgent := ""
 	for _, option := range options.CustomHeaders {
 		parts := strings.SplitN(option, ":", 2)
@@ -103,12 +105,20 @@ func New(options *types.Options) (*Browser, error) {
 		}
 		if strings.EqualFold(parts[0], "User-Agent") {
 			customAgent = parts[1]
+		} else {
+			k := strings.TrimSpace(parts[0])
+			v := strings.TrimSpace(parts[1])
+			if k == "" || v == "" {
+				continue
+			}
+			defaultHeaders[k] = v
 		}
 	}
 
 	engine := &Browser{
 		tempDir:        dataStore,
 		customAgent:    customAgent,
+		defaultHeaders: defaultHeaders,
 		engine:         browser,
 		options:        options,
 		httpClientOnce: &sync.Once{},
@@ -135,6 +145,30 @@ func (b *Browser) UserAgent() string {
 	return b.customAgent
 }
 
+// applyDefaultHeaders setsheaders passed via cli -H flag
+func (b *Browser) applyDefaultHeaders(p *rod.Page) error {
+	pairs := make([]string, 0, len(b.defaultHeaders)*2+2)
+
+	hasAcceptLanguage := false
+	for k := range b.defaultHeaders {
+		if strings.EqualFold(k, "Accept-Language") {
+			hasAcceptLanguage = true
+			break
+		}
+	}
+	if !hasAcceptLanguage {
+		pairs = append(pairs, "Accept-Language", "en, en-GB, en-us;")
+	}
+	for k, v := range b.defaultHeaders {
+		pairs = append(pairs, k, v)
+	}
+	if len(pairs) == 0 {
+		return nil
+	}
+	_, err := p.SetExtraHeaders(pairs)
+	return err
+}
+
 func (b *Browser) getHTTPClient() (*http.Client, error) {
 	var err error
 	b.httpClientOnce.Do(func() {
@@ -147,6 +181,6 @@ func (b *Browser) getHTTPClient() (*http.Client, error) {
 func (b *Browser) Close() {
 	_ = b.engine.Close()
 	b.launcher.Kill()
-	os.RemoveAll(b.tempDir)
+	_ = os.RemoveAll(b.tempDir)
 	processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs)
 }
diff --git a/pkg/protocols/headless/engine/page.go b/pkg/protocols/headless/engine/page.go
index 6986f80b..244d740c 100644
--- a/pkg/protocols/headless/engine/page.go
+++ b/pkg/protocols/headless/engine/page.go
@@ -61,6 +61,10 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map
 	}
 	page = page.Timeout(options.Timeout)
 
+	if err = i.browser.applyDefaultHeaders(page); err != nil {
+		return nil, nil, err
+	}
+
 	if i.browser.customAgent != "" {
 		if userAgentErr := page.SetUserAgent(&proto.NetworkSetUserAgentOverride{UserAgent: i.browser.customAgent}); userAgentErr != nil {
 			return nil, nil, userAgentErr
@@ -130,10 +134,6 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map
 		return nil, nil, err
 	}
 
-	if _, err := page.SetExtraHeaders([]string{"Accept-Language", "en, en-GB, en-us;"}); err != nil {
-		return nil, nil, err
-	}
-
 	// inject cookies
 	// each http request is performed via the native go http client
 	// we first inject the shared cookies
diff --git a/pkg/protocols/http/request_fuzz.go b/pkg/protocols/http/request_fuzz.go
index 045dec33..3a7e2cc7 100644
--- a/pkg/protocols/http/request_fuzz.go
+++ b/pkg/protocols/http/request_fuzz.go
@@ -76,7 +76,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous
 			if errors.Is(err, ErrMissingVars) {
 				return err
 			}
-			gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed : %s\n", request.options.TemplateID, err)
+			gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err)
 		}
 		return nil
 	}
@@ -103,13 +103,13 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous
 		// in case of any error, return it
 		if fuzz.IsErrRuleNotApplicable(err) {
 			// log and fail silently
-			gologger.Verbose().Msgf("[%s] fuzz: rule not applicable : %s\n", request.options.TemplateID, err)
+			gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)
 			return nil
 		}
 		if errors.Is(err, ErrMissingVars) {
 			return err
 		}
-		gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed : %s\n", request.options.TemplateID, err)
+		gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err)
 	}
 	return nil
 }
@@ -158,7 +158,7 @@ func (request *Request) executeAllFuzzingRules(input *contextargs.Context, value
 			continue
 		}
 		if fuzz.IsErrRuleNotApplicable(err) {
-			gologger.Verbose().Msgf("[%s] fuzz: rule not applicable : %s\n", request.options.TemplateID, err)
+			gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)
 			continue
 		}
 		if err == types.ErrNoMoreRequests {
@@ -168,8 +168,9 @@ func (request *Request) executeAllFuzzingRules(input *contextargs.Context, value
 	}
 
 	if !applicable {
-		return fuzz.ErrRuleNotApplicable.Msgf(fmt.Sprintf("no rule was applicable for this request: %v", input.MetaInput.Input))
+		return fmt.Errorf("no rule was applicable for this request: %v", input.MetaInput.Input)
 	}
+
 	return nil
 }
 
diff --git a/pkg/protocols/offlinehttp/operators.go b/pkg/protocols/offlinehttp/operators.go
index cbce83cb..3164c086 100644
--- a/pkg/protocols/offlinehttp/operators.go
+++ b/pkg/protocols/offlinehttp/operators.go
@@ -109,7 +109,7 @@ func (request *Request) responseToDSLMap(resp *http.Response, host, matched, raw
 		data[strings.ToLower(cookie.Name)] = cookie.Value
 	}
 	for k, v := range resp.Header {
-		k = strings.ToLower(strings.TrimSpace(k))
+		k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_"))
 		data[k] = strings.Join(v, " ")
 	}
 
diff --git a/pkg/reporting/trackers/linear/linear.go b/pkg/reporting/trackers/linear/linear.go
index 243baefe..75619c14 100644
--- a/pkg/reporting/trackers/linear/linear.go
+++ b/pkg/reporting/trackers/linear/linear.go
@@ -54,15 +54,17 @@ type Options struct {
 
 // New creates a new issue tracker integration client based on options.
 func New(options *Options) (*Integration, error) {
+	var transport = http.DefaultTransport
+	if options.HttpClient != nil && options.HttpClient.HTTPClient.Transport != nil {
+		transport = options.HttpClient.HTTPClient.Transport
+	}
+
 	httpClient := &http.Client{
 		Transport: &addHeaderTransport{
-			T:   http.DefaultTransport,
+			T:   transport,
 			Key: options.APIKey,
 		},
 	}
-	if options.HttpClient != nil {
-		httpClient.Transport = options.HttpClient.HTTPClient.Transport
-	}
 
 	integration := &Integration{
 		url:        "https://api.linear.app/graphql",
diff --git a/pkg/scan/events/stats_build.go b/pkg/scan/events/stats_build.go
index 41901cc5..7d03e42e 100644
--- a/pkg/scan/events/stats_build.go
+++ b/pkg/scan/events/stats_build.go
@@ -25,7 +25,7 @@ type ScanStatsWorker struct {
 	m         *sync.Mutex
 	directory string
 	file      *os.File
-	enc       *json.Encoder
+	enc       json.Encoder
 }
 
 // Init initializes the scan stats worker
diff --git a/pkg/templates/parser.go b/pkg/templates/parser.go
index 56b64c23..3a2cabc2 100644
--- a/pkg/templates/parser.go
+++ b/pkg/templates/parser.go
@@ -131,13 +131,13 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
 		_ = reader.Close()
 	}()
 
-	data, err := io.ReadAll(reader)
-	if err != nil {
-		return nil, err
-	}
-
-	// pre-process directives only for local files
+	// For local YAML files, check if preprocessing is needed
+	var data []byte
 	if fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML {
+		data, err = io.ReadAll(reader)
+		if err != nil {
+			return nil, err
+		}
 		data, err = yamlutil.PreProcess(data)
 		if err != nil {
 			return nil, err
@@ -148,12 +148,28 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
 
 	switch config.GetTemplateFormatFromExt(templatePath) {
 	case config.JSON:
+		if data == nil {
+			data, err = io.ReadAll(reader)
+			if err != nil {
+				return nil, err
+			}
+		}
 		err = json.Unmarshal(data, template)
 	case config.YAML:
-		if p.NoStrictSyntax {
-			err = yaml.Unmarshal(data, template)
+		if data != nil {
+			// Already read and preprocessed
+			if p.NoStrictSyntax {
+				err = yaml.Unmarshal(data, template)
+			} else {
+				err = yaml.UnmarshalStrict(data, template)
+			}
 		} else {
-			err = yaml.UnmarshalStrict(data, template)
+			// Stream directly from reader
+			decoder := yaml.NewDecoder(reader)
+			if !p.NoStrictSyntax {
+				decoder.SetStrict(true)
+			}
+			err = decoder.Decode(template)
 		}
 	default:
 		err = fmt.Errorf("failed to identify template format expected JSON or YAML but got %v", templatePath)
@@ -162,7 +178,7 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
 		return nil, err
 	}
 
-	p.parsedTemplatesCache.Store(templatePath, template, data, nil)
+	p.parsedTemplatesCache.Store(templatePath, template, nil, nil) // don't keep raw bytes to save memory
 	return template, nil
 }
 
diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go
index 7726fc7c..fbbfd44e 100644
--- a/pkg/templates/templates.go
+++ b/pkg/templates/templates.go
@@ -7,7 +7,6 @@ import (
 	"strconv"
 	"strings"
 
-	validate "github.com/go-playground/validator/v10"
 	"github.com/projectdiscovery/nuclei/v3/pkg/model"
 	"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
 	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
@@ -310,10 +309,8 @@ func (template *Template) validateAllRequestIDs() {
 // MarshalYAML forces recursive struct validation during marshal operation
 func (template *Template) MarshalYAML() ([]byte, error) {
 	out, marshalErr := yaml.Marshal(template)
-	// Review: we are adding requestIDs for templateContext
-	// if we are using this method then we might need to purge manually added IDS that start with `templatetype_`
-	// this is only applicable if there are more than 1 request fields in protocol
-	errValidate := validate.New().Struct(template)
+	// Use shared validator to avoid rebuilding struct cache for every template marshal
+	errValidate := tplValidator.Struct(template)
 	return out, multierr.Append(marshalErr, errValidate)
 }
 
@@ -354,7 +351,7 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error
 	if len(alias.RequestsWithTCP) > 0 {
 		template.RequestsNetwork = alias.RequestsWithTCP
 	}
-	err = validate.New().Struct(template)
+	err = tplValidator.Struct(template)
 	if err != nil {
 		return err
 	}
@@ -525,7 +522,7 @@ func (template *Template) hasMultipleRequests() bool {
 func (template *Template) MarshalJSON() ([]byte, error) {
 	type TemplateAlias Template //avoid recursion
 	out, marshalErr := json.Marshal((*TemplateAlias)(template))
-	errValidate := validate.New().Struct(template)
+	errValidate := tplValidator.Struct(template)
 	return out, multierr.Append(marshalErr, errValidate)
 }
 
@@ -538,7 +535,7 @@ func (template *Template) UnmarshalJSON(data []byte) error {
 		return err
 	}
 	*template = Template(*alias)
-	err = validate.New().Struct(template)
+	err = tplValidator.Struct(template)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/templates/validator_singleton.go b/pkg/templates/validator_singleton.go
new file mode 100644
index 00000000..9679ac21
--- /dev/null
+++ b/pkg/templates/validator_singleton.go
@@ -0,0 +1,7 @@
+package templates
+
+import (
+	validate "github.com/go-playground/validator/v10"
+)
+
+var tplValidator = validate.New()



Test patch:
diff --git a/pkg/fuzz/fuzz_test.go b/pkg/fuzz/fuzz_test.go
index 6ef2e39b..a4f5186c 100644
--- a/pkg/fuzz/fuzz_test.go
+++ b/pkg/fuzz/fuzz_test.go
@@ -3,6 +3,11 @@ package fuzz
 import (
 	"testing"
 
+	"github.com/projectdiscovery/goflags"
+	"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables"
+	"github.com/projectdiscovery/nuclei/v3/pkg/types"
+	"github.com/projectdiscovery/nuclei/v3/pkg/utils"
 	"github.com/stretchr/testify/require"
 )
 
@@ -37,3 +42,219 @@ func TestRuleMatchKeyOrValue(t *testing.T) {
 		require.False(t, result, "could not get correct result")
 	})
 }
+
+func TestEvaluateVariables(t *testing.T) {
+	t.Run("keys", func(t *testing.T) {
+		rule := &Rule{
+			Keys: []string{"{{foo_var}}"},
+			Part: "query",
+		}
+
+		// mock
+		templateVars := variables.Variable{
+			InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+		}
+		templateVars.Set("foo_var", "foo_var_value")
+
+		constants := map[string]interface{}{
+			"const_key": "const_value",
+		}
+
+		options := &types.Options{}
+
+		// runtime vars (to simulate CLI)
+		runtimeVars := goflags.RuntimeMap{}
+		_ = runtimeVars.Set("runtime_key=runtime_value")
+		options.Vars = runtimeVars
+
+		executorOpts := &protocols.ExecutorOptions{
+			Variables: templateVars,
+			Constants: constants,
+			Options:   options,
+		}
+
+		err := rule.Compile(nil, executorOpts)
+		require.NoError(t, err, "could not compile rule")
+
+		result := rule.matchKeyOrValue("foo_var_value", "test_value")
+		require.True(t, result, "should match evaluated variable key")
+
+		result = rule.matchKeyOrValue("{{foo_var}}", "test_value")
+		require.False(t, result, "should not match unevaluated variable key")
+	})
+
+	t.Run("keys-regex", func(t *testing.T) {
+		rule := &Rule{
+			KeysRegex: []string{"^{{foo_var}}"},
+			Part:      "query",
+		}
+
+		templateVars := variables.Variable{
+			InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+		}
+		templateVars.Set("foo_var", "foo_var_value")
+
+		executorOpts := &protocols.ExecutorOptions{
+			Variables: templateVars,
+			Constants: map[string]interface{}{},
+			Options:   &types.Options{},
+		}
+
+		err := rule.Compile(nil, executorOpts)
+		require.NoError(t, err, "could not compile rule")
+
+		result := rule.matchKeyOrValue("foo_var_value", "test_value")
+		require.True(t, result, "should match evaluated variable in regex")
+
+		result = rule.matchKeyOrValue("other_key", "test_value")
+		require.False(t, result, "should not match non-matching key")
+	})
+
+	t.Run("values-regex", func(t *testing.T) {
+		rule := &Rule{
+			ValuesRegex: []string{"{{foo_var}}"},
+			Part:        "query",
+		}
+
+		templateVars := variables.Variable{
+			InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+		}
+		templateVars.Set("foo_var", "test_pattern")
+
+		executorOpts := &protocols.ExecutorOptions{
+			Variables: templateVars,
+			Constants: map[string]interface{}{},
+			Options:   &types.Options{},
+		}
+
+		err := rule.Compile(nil, executorOpts)
+		require.NoError(t, err, "could not compile rule")
+
+		result := rule.matchKeyOrValue("test_key", "test_pattern")
+		require.True(t, result, "should match evaluated variable in values regex")
+
+		result = rule.matchKeyOrValue("test_key", "other_value")
+		require.False(t, result, "should not match non-matching value")
+	})
+
+	// complex vars w/ consts and runtime vars
+	t.Run("complex-variables", func(t *testing.T) {
+		rule := &Rule{
+			Keys: []string{"{{template_var}}", "{{const_key}}", "{{runtime_key}}"},
+			Part: "query",
+		}
+
+		templateVars := variables.Variable{
+			InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+		}
+		templateVars.Set("template_var", "template_value")
+
+		constants := map[string]interface{}{
+			"const_key": "const_value",
+		}
+
+		options := &types.Options{}
+		runtimeVars := goflags.RuntimeMap{}
+		_ = runtimeVars.Set("runtime_key=runtime_value")
+		options.Vars = runtimeVars
+
+		executorOpts := &protocols.ExecutorOptions{
+			Variables: templateVars,
+			Constants: constants,
+			Options:   options,
+		}
+
+		err := rule.Compile(nil, executorOpts)
+		require.NoError(t, err, "could not compile rule")
+
+		result := rule.matchKeyOrValue("template_value", "test")
+		require.True(t, result, "should match template variable")
+
+		result = rule.matchKeyOrValue("const_value", "test")
+		require.True(t, result, "should match constant")
+
+		result = rule.matchKeyOrValue("runtime_value", "test")
+		require.True(t, result, "should match runtime variable")
+
+		result = rule.matchKeyOrValue("{{template_var}}", "test")
+		require.False(t, result, "should not match unevaluated template variable")
+	})
+
+	t.Run("invalid-variables", func(t *testing.T) {
+		rule := &Rule{
+			Keys: []string{"{{nonexistent_var}}"},
+			Part: "query",
+		}
+
+		executorOpts := &protocols.ExecutorOptions{
+			Variables: variables.Variable{
+				InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(0),
+			},
+			Constants: map[string]interface{}{},
+			Options:   &types.Options{},
+		}
+
+		err := rule.Compile(nil, executorOpts)
+		if err != nil {
+			require.Contains(t, err.Error(), "unresolved", "error should mention unresolved variables")
+		} else {
+			result := rule.matchKeyOrValue("some_key", "some_value")
+			require.False(t, result, "should not match when variables are unresolved")
+		}
+	})
+
+	t.Run("evaluateVars-function", func(t *testing.T) {
+		rule := &Rule{}
+
+		templateVars := variables.Variable{
+			InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+		}
+		templateVars.Set("test_var", "test_value")
+
+		constants := map[string]interface{}{
+			"const_var": "const_value",
+		}
+
+		options := &types.Options{}
+		runtimeVars := goflags.RuntimeMap{}
+		_ = runtimeVars.Set("runtime_var=runtime_value")
+		options.Vars = runtimeVars
+
+		executorOpts := &protocols.ExecutorOptions{
+			Variables: templateVars,
+			Constants: constants,
+			Options:   options,
+		}
+
+		rule.options = executorOpts
+
+		// Test simple var substitution
+		result, err := rule.evaluateVars("{{test_var}}")
+		require.NoError(t, err, "should evaluate template variable")
+		require.Equal(t, "test_value", result, "should return evaluated value")
+
+		// Test constant substitution
+		result, err = rule.evaluateVars("{{const_var}}")
+		require.NoError(t, err, "should evaluate constant")
+		require.Equal(t, "const_value", result, "should return constant value")
+
+		// Test runtime var substitution
+		result, err = rule.evaluateVars("{{runtime_var}}")
+		require.NoError(t, err, "should evaluate runtime variable")
+		require.Equal(t, "runtime_value", result, "should return runtime value")
+
+		// Test mixed content
+		result, err = rule.evaluateVars("prefix-{{test_var}}-suffix")
+		require.NoError(t, err, "should evaluate mixed content")
+		require.Equal(t, "prefix-test_value-suffix", result, "should return mixed evaluated content")
+
+		// Test unresolved var - should either fail during evaluation or return original string
+		result2, err := rule.evaluateVars("{{nonexistent}}")
+		if err != nil {
+			require.Contains(t, err.Error(), "unresolved", "should fail for unresolved variable")
+		} else {
+			// If no error, it should return the original unresolved variable
+			require.Equal(t, "{{nonexistent}}", result2, "should return original string for unresolved variable")
+		}
+	})
+}
diff --git a/pkg/installer/template_test.go b/pkg/installer/template_test.go
index 952c4459..435797b1 100644
--- a/pkg/installer/template_test.go
+++ b/pkg/installer/template_test.go
@@ -59,3 +59,42 @@ func TestTemplateInstallation(t *testing.T) {
 	require.FileExists(t, config.DefaultConfig.GetIgnoreFilePath())
 	t.Logf("Installed %d templates", counter)
 }
+
+func TestIsOutdatedVersion(t *testing.T) {
+	testCases := []struct {
+		current  string
+		latest   string
+		expected bool
+		desc     string
+	}{
+		// Test the empty latest version case (main bug fix)
+		{"v10.2.7", "", false, "Empty latest version should not trigger update"},
+
+		// Test same versions
+		{"v10.2.7", "v10.2.7", false, "Same versions should not trigger update"},
+
+		// Test outdated version
+		{"v10.2.6", "v10.2.7", true, "Older version should trigger update"},
+
+		// Test newer current version (edge case)
+		{"v10.2.8", "v10.2.7", false, "Newer current version should not trigger update"},
+
+		// Test dev versions
+		{"v10.2.7-dev", "v10.2.7", false, "Dev version matching release should not trigger update"},
+		{"v10.2.6-dev", "v10.2.7", true, "Outdated dev version should trigger update"},
+
+		// Test invalid semver fallback
+		{"invalid-version", "v10.2.7", true, "Invalid current version should trigger update (fallback)"},
+		{"v10.2.7", "invalid-version", true, "Invalid latest version should trigger update (fallback)"},
+		{"same-invalid", "same-invalid", false, "Same invalid versions should not trigger update (fallback)"},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result := config.IsOutdatedVersion(tc.current, tc.latest)
+			require.Equal(t, tc.expected, result,
+				"IsOutdatedVersion(%q, %q) = %t, expected %t",
+				tc.current, tc.latest, result, tc.expected)
+		})
+	}
+}
diff --git a/pkg/output/stats/waf/waf_test.go b/pkg/output/stats/waf/waf_test.go
index 0698b3a4..f3b2b868 100644
--- a/pkg/output/stats/waf/waf_test.go
+++ b/pkg/output/stats/waf/waf_test.go
@@ -1,6 +1,9 @@
 package waf
 
-import "testing"
+import (
+	"regexp"
+	"testing"
+)
 
 func TestWAFDetection(t *testing.T) {
 	detector := NewWafDetector()
@@ -58,3 +61,61 @@ func TestWAFDetection(t *testing.T) {
 		})
 	}
 }
+
+func TestWAFDetectionNilPointerSafety(t *testing.T) {
+	tests := []struct {
+		name     string
+		detector *WafDetector
+		content  string
+	}{
+		{
+			name:     "nil detector",
+			detector: nil,
+			content:  "test content",
+		},
+		{
+			name: "nil regexCache",
+			detector: &WafDetector{
+				wafs:       make(map[string]waf),
+				regexCache: nil,
+			},
+			content: "test content",
+		},
+		{
+			name: "regexCache with nil regex",
+			detector: &WafDetector{
+				wafs: make(map[string]waf),
+				regexCache: map[string]*regexp.Regexp{
+					"test": nil,
+				},
+			},
+			content: "test content",
+		},
+		{
+			name: "empty regexCache",
+			detector: &WafDetector{
+				wafs:       make(map[string]waf),
+				regexCache: make(map[string]*regexp.Regexp),
+			},
+			content: "test content",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			defer func() {
+				if r := recover(); r != nil {
+					t.Errorf("DetectWAF panicked with nil pointer: %v", r)
+				}
+			}()
+
+			waf, matched := tt.detector.DetectWAF(tt.content)
+			if matched {
+				t.Errorf("expected no match for nil pointer case, got match=true, waf=%s", waf)
+			}
+			if waf != "" {
+				t.Errorf("expected empty WAF string for nil pointer case, got waf=%s", waf)
+			}
+		})
+	}
+}
diff --git a/pkg/protocols/offlinehttp/operators_test.go b/pkg/protocols/offlinehttp/operators_test.go
index 21bb8693..4fb8c39f 100644
--- a/pkg/protocols/offlinehttp/operators_test.go
+++ b/pkg/protocols/offlinehttp/operators_test.go
@@ -134,7 +134,7 @@ func TestHTTPOperatorExtract(t *testing.T) {
 	event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
 	require.Len(t, event, 14, "could not get correct number of items in dsl map")
 	require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
-	require.Equal(t, "Test-Response", event["test-header"], "could not get correct resp for header")
+	require.Equal(t, "Test-Response", event["test_header"], "could not get correct resp for header")
 
 	t.Run("extract", func(t *testing.T) {
 		extractor := &extractors.Extractor{
@@ -153,7 +153,7 @@ func TestHTTPOperatorExtract(t *testing.T) {
 	t.Run("kval", func(t *testing.T) {
 		extractor := &extractors.Extractor{
 			Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},
-			KVal: []string{"test-header"},
+			KVal: []string{"test_header"},
 			Part: "header",
 		}
 		err = extractor.CompileExtractors()