[BUG] RequestResponse.MarshalJSON emits base64 strings that its own UnmarshalJSON cannot decode (JSONCodec round-trip broken)
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/responsemust 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 base64null
Steps To Reproduce
- Save the repro as
pkg/input/types/repro_test.goondev:
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) } } `
- Run:
go test -run TestRepro ./pkg/input/types/ - Observe the failure (see log output below)
Relevant log output
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.goEnvironment
- OS: Kali Linux 2026.2
- Nuclei: dev @ c5611b9a (built from source; bug also confirmed on upstream dev @ 47f74706)
- Go: go 1.27.1 linux/amd64Anything 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).
Source: projectdiscovery/nuclei