From 7748411200fb15e71fcacd6a148f14dc91a3ae61 Mon Sep 17 00:00:00 2001 From: anant <170962632+antcybersec@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:59:47 +0530 Subject: [PATCH] fix(cmd): keep "#" in -H header values Signed-off-by: anant <170962632+antcybersec@users.noreply.github.com> --- cmd/nuclei/header_test.go | 57 +++++++++++++++++++++++++++++++++++++++ cmd/nuclei/main.go | 18 ++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 cmd/nuclei/header_test.go diff --git a/cmd/nuclei/header_test.go b/cmd/nuclei/header_test.go new file mode 100644 index 000000000..ba73b9fd5 --- /dev/null +++ b/cmd/nuclei/header_test.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v3/pkg/types" + "github.com/stretchr/testify/require" +) + +const headerChildEnv = "NUCLEI_HEADER_CHILD_ARGS" + +// -H values can contain "#", on the command line, in a header file and in the +// config file, while lines of a header file that start with "#" are skipped as +// comments. +func TestHeaderValuesKeepHash(t *testing.T) { + if args := os.Getenv(headerChildEnv); args != "" { + options = &types.Options{Logger: gologger.DefaultLogger} + os.Args = append([]string{os.Args[0]}, strings.Split(args, "\n")...) + readConfig() + fmt.Printf("headers=%s\n", strings.Join(options.CustomHeaders, "|")) + os.Exit(0) + } + + root := t.TempDir() + headerFile := filepath.Join(root, "headers.txt") + require.NoError(t, os.WriteFile(headerFile, []byte("# auth headers\nX-Token: abc#def\nReferer: https://app.example.com/#/dashboard\n"), 0o600)) + configFile := filepath.Join(root, "config.yaml") + require.NoError(t, os.WriteFile(configFile, []byte("header:\n - \"X-Config: cfg#value\"\n"), 0o600)) + + tests := []struct { + name string + args []string + want string + }{ + {"cli and header file", []string{"-H", "Cookie: sid=1; color=#ff0000", "-H", headerFile}, "Cookie: sid=1; color=#ff0000|X-Token: abc#def|Referer: https://app.example.com/#/dashboard"}, + {"config file", []string{"-config", configFile}, "X-Config: cfg#value"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempRoot := filepath.Join(t.TempDir(), "tmp") + require.NoError(t, os.Mkdir(tempRoot, 0o700)) + + args := append(tt.args, "-disable-update-check") + cmd := exec.Command(os.Args[0], "-test.run=^TestHeaderValuesKeepHash$", "-test.count=1") + cmd.Env = isolatedNucleiStorageEnv(root, tempRoot, headerChildEnv+"="+strings.Join(args, "\n")) + output, err := cmd.CombinedOutput() + require.NoError(t, err, string(output)) + require.Contains(t, string(output), "headers="+tt.want+"\n") + }) + } +} diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index 39d4d3a26..234442e95 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -55,6 +55,17 @@ var ( inlineSecretsTempMu sync.Mutex ) +// normalizeHeaderValue trims a -H value and drops it when the whole line is a +// "#" comment. The goflags default strips inline comments, which cut values +// such as "Cookie: color=#fff" at the "#". +func normalizeHeaderValue(s string) string { + s = strings.TrimSpace(s) + if strings.HasPrefix(s, "#") { + return "" + } + return s +} + func main() { options.Logger = gologger.DefaultLogger @@ -255,6 +266,11 @@ func readConfig() *goflags.FlagSet { var pdcpAuth string var fuzzFlag bool + // FileStringSliceOptions as set up by runner.ConfigureOptions, but keeping + // "#" inside -H values + headerSliceOptions := goflags.FileStringSliceOptions + headerSliceOptions.Normalize = normalizeHeaderValue + flagSet := goflags.NewFlagSet() flagSet.CaseSensitive = true flagSet.SetDescription(`Nuclei is a fast, template based vulnerability scanner focusing @@ -367,7 +383,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.IntVarP(&options.MaxRedirects, "max-redirects", "mr", 10, "max number of redirects to follow for http templates"), flagSet.BoolVarP(&options.DisableRedirects, "disable-redirects", "dr", false, "disable redirects for http templates"), flagSet.StringVarP(&options.ReportingConfig, "report-config", "rc", "", "nuclei reporting module configuration file"), // TODO merge into the config file or rename to issue-tracking - flagSet.StringSliceVarP(&options.CustomHeaders, "header", "H", nil, "custom header/cookie to include in all http request in header:value format (cli, file)", goflags.FileStringSliceOptions), + flagSet.StringSliceVarP(&options.CustomHeaders, "header", "H", nil, "custom header/cookie to include in all http request in header:value format (cli, file)", headerSliceOptions), flagSet.RuntimeMapVarP(&options.Vars, "var", "V", nil, "custom vars in key=value format"), flagSet.StringVarP(&options.ResolversFile, "resolvers", "r", "", "file containing resolver list for nuclei"), flagSet.BoolVarP(&options.SystemResolvers, "system-resolvers", "sr", false, "use system DNS resolving as error fallback"),