From 852c79941d66f1cbd2959a8920092a5a0c1125ea Mon Sep 17 00:00:00 2001 From: nova-loop Date: Mon, 9 Nov 2026 10:03:07 +0800 Subject: [PATCH] fix(config): print the effective attempts value in -print-config (#153) --- CHANGELOG.md | 1 + main.go | 20 +++++++++++++++----- main_config.go | 31 ++++++++++++++++++++++++++++++- main_config_test.go | 27 +++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fda050..190b677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The scan engine always ends with exactly one done event, marked `Stopped` when the scan ended before testing any candidate (out of scope, recursion ceiling, failed preflight or wildcard check, a wildcard zone without `-force`, or Ctrl+C during those checks), so the CLI, the TUI and `pkg/subenum` handle one shape of ending instead of a channel that sometimes closes without one. `pkg/subenum`'s `Event` gains `Stopped`, and a stopped scan's `Stats` now include the queries its checks sent (#99). ### Fixed +- `-print-config` shows the `attempts` value a scan actually uses: `1` by default instead of the raw `0` sentinel, and the `-retries` value (marked `from retries`) when that is what wins (#153). - `tools/wordlist-gen` parses flags that appear after a positional the same way `subenum` does, instead of silently ignoring both: a leftover argument is now a usage error (2) that points at `-domain`, and an invalid `-combine` value or an empty `-o` exits 2 instead of 1 (#120). ## [0.9.0] - 2026-10-06 diff --git a/main.go b/main.go index 0a568a7..e7ce69a 100644 --- a/main.go +++ b/main.go @@ -1170,6 +1170,17 @@ func scanTarget(ctx context.Context, f cliFlags, domain string, entries []string // an env value over the config file); both at the same level is an error // (#116). func resolveAttempts(attempts, retries int, attemptsSrc, retriesSrc string) (int, error) { + n, fromRetries, err := effectiveAttempts(attempts, retries, attemptsSrc, retriesSrc) + if err == nil && fromRetries { + fmt.Fprintln(os.Stderr, "Warning: -retries is deprecated, use -attempts instead") + } + return n, err +} + +// effectiveAttempts is resolveAttempts without the deprecation warning. It +// also reports whether the value came from -retries, so -print-config can +// show the value the scanner will actually use. +func effectiveAttempts(attempts, retries int, attemptsSrc, retriesSrc string) (n int, fromRetries bool, err error) { attemptsSet := attempts != 0 retriesSet := retries != 0 if attemptsSet && retriesSet { @@ -1183,13 +1194,12 @@ func resolveAttempts(attempts, retries int, attemptsSrc, retriesSrc string) (int switch { case attemptsSet && retriesSet: - return 0, fmt.Errorf("cannot use both -attempts and -retries; use -attempts only") + return 0, false, fmt.Errorf("cannot use both -attempts and -retries; use -attempts only") case retriesSet: - fmt.Fprintln(os.Stderr, "Warning: -retries is deprecated, use -attempts instead") - return retries, nil + return retries, true, nil case attemptsSet: - return attempts, nil + return attempts, false, nil default: - return 1, nil + return 1, false, nil } } diff --git a/main_config.go b/main_config.go index ef50c13..45421e4 100644 --- a/main_config.go +++ b/main_config.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "sort" + "strconv" "strings" ) @@ -212,10 +213,38 @@ func (d *defaultsLoader) printConfig(w io.Writer, fset *flag.FlagSet) { if given[fl.Name] { source = sourceFlag } - _, _ = fmt.Fprintf(w, "%-14s = %-24q (%s)\n", fl.Name, fl.Value.String(), source) + value := fl.Value.String() + if fl.Name == "attempts" { + value, source = d.effectiveAttemptsSetting(fset, value, source) + } + _, _ = fmt.Fprintf(w, "%-14s = %-24q (%s)\n", fl.Name, value, source) }) } +// effectiveAttemptsSetting resolves -attempts the way a scan does: 0 means +// "unset", so the effective value is 1 or whatever -retries supplies. When +// the two conflict the raw value is kept, since a scan would refuse to run. +func (d *defaultsLoader) effectiveAttemptsSetting(fset *flag.FlagSet, value, source string) (string, string) { + retriesFlag := fset.Lookup("retries") + if retriesFlag == nil { + return value, source + } + attempts, err1 := strconv.Atoi(value) + retries, err2 := strconv.Atoi(retriesFlag.Value.String()) + if err1 != nil || err2 != nil { + return value, source + } + retriesSrc := d.source(fset, "retries") + n, fromRetries, err := effectiveAttempts(attempts, retries, source, retriesSrc) + if err != nil { + return value, source + } + if fromRetries { + source = retriesSrc + ", from retries" + } + return strconv.Itoa(n), source +} + // settings returns the effective value of every setting that came from the // environment or the config file, keyed by flag name, for the TUI's form. func (d *defaultsLoader) settings(fs *flag.FlagSet) map[string]string { diff --git a/main_config_test.go b/main_config_test.go index 0dc4e4e..f3784ea 100644 --- a/main_config_test.go +++ b/main_config_test.go @@ -214,3 +214,30 @@ func TestFileFlagsAreComplete(t *testing.T) { } } } + +// -print-config shows the attempts value a scan will use, not the raw 0 +// sentinel (#153). +func TestPrintConfigEffectiveAttempts(t *testing.T) { + for _, tc := range []struct { + args []string + env map[string]string + want string + }{ + {nil, nil, `attempts = "1" (default)`}, + {[]string{"-attempts", "4"}, nil, `attempts = "4" (flag)`}, + {[]string{"-retries", "3"}, nil, `attempts = "3" (flag, from retries)`}, + {nil, map[string]string{"SUBENUM_RETRIES": "2"}, `attempts = "2" (env, from retries)`}, + {[]string{"-attempts", "5"}, map[string]string{"SUBENUM_RETRIES": "2"}, `attempts = "5" (flag)`}, + } { + d := &defaultsLoader{getenv: func(k string) string { return tc.env[k] }} + _, _, fs, err := parseFlagsWith(append(tc.args, "example.com"), d.apply) + if err != nil { + t.Fatal(err) + } + var out bytes.Buffer + d.printConfig(&out, fs) + if !strings.Contains(out.String(), tc.want) { + t.Errorf("%v %v: -print-config lacks %q:\n%s", tc.args, tc.env, tc.want, out.String()) + } + } +}