#7795·nuclei

[BUG] RequestResponse.MarshalJSON emits base64 strings that its own UnmarshalJSON cannot decode (JSONCodec round-trip broken)

Author: comradezephyrCreated Sep 28, 2026Updated Sep 28, 2026
LabelsType: Bug

Is there an existing issue for this?

  • I have searched the existing issues.

Current Behavior

RequestResponse claims to be a full JSONCodec (pkg/input/types/http.go:23: var _ json.JSONCodec = &RequestResponse{}), but MarshalJSON stores the pre-marshaled request/response bytes as []byte inside map[string]interface{}:

​go m["request"] = reqBin // []byte -> encoding/json (and sonic) encode as a base64 STRING ​

UnmarshalJSON expects JSON objects under those keys, so the marshal → unmarshal round-trip always fails when Request != nil:

marshaled: {"request":"eyJtZXRob2QiOiJHRVQifQ==","response":"bnVsbA==","url":"example.com/path?q=1"}
round-trip failed: Mismatch type types.HttpRequest with value string "at index 0: mismatched type with value ..."

"response" is equally broken — even a nil Response is emitted as base64 "bnVsbA==" (base64 of null).

Impact path: MetaInput.ReqResp serializes under the raw-request key with this codec (MetaInput.MarshalString) and is read back by MetaInput.Unmarshal. That pair is used by pkg/input/provider/list/hmap.go (every host-map scan + exclusion processing, lines 132/568/627) and internal/runner/preflight_portscan.go. With a captured request attached, Unmarshal errors out — Iterate propagates the failure mid-scan, and the exclusion paths silently skip the key.

Expected Behavior

json.Marshal(rr) output should always be decodable by json.Unmarshal(..., &rr):

  • request/response must be emitted as JSON objects, not base64 strings
  • nil parts should be omitted (mirroring UnmarshalJSON, which only reads those keys when present) instead of being encoded as base64 null

Steps To Reproduce

  1. Save the repro as pkg/input/types/repro_test.go on dev:

​go package types ' import ( "testing"

"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"

)

func TestRepro(t *testing.T) { rr, _ := ParseRawRequest("GET /path HTTP/1.1\r\nHost: example.com\r\n\r\n") out, _ := json.Marshal(rr) var back RequestResponse if err := json.Unmarshal(out, &back); err != nil { t.Fatalf("round-trip failed: %v\njson: %s", err, out) } } ​`

  1. Run: go test -run TestRepro ./pkg/input/types/
  2. Observe the failure (see log output below)

Relevant log output

bash
cat > pkg/input/types/repro_test.go <<'EOF'
package types

import (
	"testing"

	"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)

func TestRepro(t *testing.T) {
	rr, err := ParseRawRequest("GET /path HTTP/1.1\r\nHost: example.com\r\n\r\n")
	if err != nil {
		t.Fatal(err)
	}
	out, err := json.Marshal(rr)
	if err != nil {
		t.Fatal(err)
	}
	var back RequestResponse
	if err := json.Unmarshal(out, &back); err != nil {
		t.Fatalf("round-trip failed: %v\njson: %s", err, out)
	}
}
EOF
go test -run TestRepro ./pkg/input/types/
rm pkg/input/types/repro_test.go

Environment

markdown
- OS: Kali Linux 2026.2
- Nuclei: dev @ c5611b9a (built from source; bug also confirmed on upstream dev @ 47f74706)
- Go: go 1.27.1 linux/amd64

Anything else?

No existing issue/PR matches — searched: "MarshalJSON" (only #5882, unrelated), "json.Message" (0 results), "RequestResponse round-trip"/base64 (none). Historic #5267 only fixed the url field in UnmarshalJSON; #7601 only fixed the nil-Request panic — MarshalJSON is untouched since #4477 (Dec 2023).

Suggested fix (I can open a PR with this + regression tests at both the RequestResponse and MetaInput.MarshalString→Unmarshal levels):

​go if rr.Request != nil { reqBin, err := json.Marshal(rr.Request) if err != nil { return nil, err } m["request"] = json.Message(reqBin) } if rr.Response != nil { respBin, err := json.Marshal(rr.Response) if err != nil { return nil, err } m["response"] = json.Message(respBin) } ​

No consumer depends on the old base64 output (nothing decodes it; no test asserts that wire format).