From aa7920d97e53e87201a92a05760afa8c5aafeb44 Mon Sep 17 00:00:00 2001 From: NekoPaw <152394158+sb123sb123@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:36:58 +0800 Subject: [PATCH] docs: list every CLI flag in README Signed-off-by: NekoPaw <152394158+sb123sb123@users.noreply.github.com> --- README.md | 6 +++++ main_docs_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/README.md b/README.md index ea76cf6..ed46f67 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ make help # list all targets | `-timeout ` | `1000` | DNS timeout in milliseconds, applied to each record-type lookup separately | | `-dns-server ` | `8.8.8.8:53` | DNS server address (validated on startup) | | `-attempts ` | `1` | DNS resolution attempts per subdomain (1 = no retry) | +| `-ct` | `false` | Add names from Certificate Transparency logs (crt.sh; one HTTPS request per target, no API key) as candidates and `-permute` seeds | | `-force` | `false` | Continue scanning even if wildcard DNS is detected | | `-no-abort` | `false` | Keep scanning after the 20% resolver failure-rate abort (warning is still emitted) | | `-max-queries ` | `0` | Max candidate names to test (0 = unlimited). Each name sends one query per record type per attempt (`CNAME` costs two) | @@ -272,6 +273,7 @@ make help # list all targets | `-rate ` | `0` | Max DNS queries per second on the wire, all workers combined, including every record type, retry, wildcard probe and the preflight (0 = unlimited) | | `-type ` | `A,AAAA` | Comma-separated record types to look up: `A`, `AAAA`, `CNAME` | | `-recursive` | `false` | Recursively enumerate subdomains of discovered subdomains | +| `-permute` | `false` | After each scan, scan permutations of discovered names (`api` → `api-dev`, `dev-api`, `dev.api`, `api2`, ...) | | `-depth ` | `1` | Max recursion depth when `-recursive` is set (1 = no recursion) | | `-v` | `false` | Verbose output: IPs, timings, per-query detail (stderr) | | `-progress` | `true` | Live progress line on stderr. Off by default when stderr is not a terminal; passing `-progress` there prints whole progress lines instead of redrawing one | @@ -287,8 +289,12 @@ make help # list all targets | `-stats ` | n/a | Write a JSON run-quality report (outcomes, queries sent, verdict); see [Run-quality report](#run-quality-report) | | `-sarif ` | n/a | Write takeover candidates as SARIF 2.1.0 for GitHub code scanning; needs CNAME in `-type` | | `-tui` | `false` | Launch the interactive Terminal UI | +| `-print-config` | `false` | Print every setting's effective value and source (`flag`, env, config or default), then exit | | `-version` | n/a | Print version and exit | | `-retries ` | n/a | **Deprecated** - alias for `-attempts`, prints a warning | +| `-resume ` | n/a | Resume an interrupted run from its state file; takes no other arguments | +| `-seeds ` | n/a | Results file (any `-format`) whose names also seed `-permute`; implies `-permute` | +| `-state ` | `subenum-resume.json` | Where an interrupted run saves its state for `-resume` |
diff --git a/main_docs_test.go b/main_docs_test.go index 9abee65..3380b2b 100644 --- a/main_docs_test.go +++ b/main_docs_test.go @@ -12,6 +12,7 @@ import ( // siteFlagsFile is the site's flag table, generated from the real FlagSet so // the docs cannot drift from `subenum -h` (#64). var siteFlagsFile = filepath.Join("docs", "_includes", "flags.md") +var readmeFlagsFile = "README.md" // flagsMarkdown renders every flag as a Markdown table, sorted by name. func flagsMarkdown(fs *flag.FlagSet) string { @@ -56,3 +57,64 @@ func TestSiteFlagTableUpToDate(t *testing.T) { t.Fatalf("%s is out of date with the CLI flags; run `make docs-flags`", siteFlagsFile) } } + +// TestREADMEFlagTableCoversCLI keeps the hand-written README table complete +// when a CLI flag is added. The website table is generated, but the README is +// maintained separately for quick reference (#137). +func TestREADMEFlagTableCoversCLI(t *testing.T) { + contents, err := os.ReadFile(readmeFlagsFile) //nolint:gosec // a docs file + if err != nil { + t.Fatal(err) + } + + inTable := false + tableStarted := false + documented := make(map[string]bool) + for _, line := range strings.Split(strings.ReplaceAll(string(contents), "\r\n", "\n"), "\n") { + line = strings.TrimSpace(line) + if line == "### CLI flags" { + inTable = true + continue + } + if !inTable { + continue + } + if !strings.HasPrefix(line, "|") { + if tableStarted { + break + } + continue + } + tableStarted = true + + cells := strings.SplitN(line, "|", 3) + if len(cells) < 2 { + continue + } + cell := strings.TrimSpace(cells[1]) + if !strings.HasPrefix(cell, "`-") { + continue + } + name := strings.TrimPrefix(cell, "`-") + if end := strings.IndexAny(name, " \t<`"); end >= 0 { + name = name[:end] + } + if name != "" { + documented[name] = true + } + } + if !inTable || !tableStarted { + t.Fatalf("%s has no CLI flags table", readmeFlagsFile) + } + + _, _, fs, _ := parseFlags(nil) + var missing []string + fs.VisitAll(func(fl *flag.Flag) { + if !documented[fl.Name] { + missing = append(missing, "-"+fl.Name) + } + }) + if len(missing) > 0 { + t.Fatalf("%s CLI flags table is missing flags: %s", readmeFlagsFile, strings.Join(missing, ", ")) + } +}