Repository: projectdiscovery/nuclei

Issue:
[BUG] Headers with `-` are not available as DSL variables in passive mode
### Is there an existing issue for this?

- [x] I have searched the existing issues.

### Current Behavior

The following snippet only works for active mode but not passive mode.
```
    extractors:
      - type: dsl
        name: extracted
        dsl:
          - content_type
```
On the other hand, the following one works for both
```
    extractors:
      - type: dsl
        name: extracted
        dsl:
          - server
```

### Expected Behavior

In passive mode, headers with `-` should be available as DSL variables by replacing it with `_` the same as active mode.

### Steps To Reproduce

With the following template (a.yaml):
```
id: dsl-header

info:
  name: dsl-header
  author: prodsec-team
  severity: low
  description: Extract header with DSL

http:
  - method: GET
    path:
      - "{{BaseURL}}"
    extractors:
      - type: dsl
        name: extracted
        dsl:
          - content_type
```
Active mode:
```
$ nuclei -t a.yaml -target https://www.dropbox.com/robots.txt
...
[dsl-header:extracted] [http] [low] https://www.dropbox.com/robots.txt ["text/plain"]
[INF] Scan completed in 502.050959ms. 1 matches found
...
```
Passive mode:
```
$ httpx -u https://www.dropbox.com/robots.txt -store-response
$ nuclei -t a.yaml -target output/response/www.dropbox.com/182acb6e116711bb8cd520bd216cd56bb04298d7.txt -passive
...
[INF] Scan completed in 2.64025ms. No results found
```

### Relevant log output

```shell

```

### Environment

```markdown
- OS: macOS Sequoia 15.5
- Nuclei: v3.4.4
- Go: go1.24.3
```

### Anything else?

I believe the bug is that Nuclei doesn't replace `-` with `_` at https://github.com/projectdiscovery/nuclei/blob/91adfeb91c2e8fe1e077dd5f36c045442c18009c/pkg/protocols/offlinehttp/operators.go#L112

Discussion:

Solution patch:
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, " ")
 	}
 



Test patch:
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()