You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
### Added
11
+
- Interactive terminal UI (`-tui` flag) built with Bubble Tea — form-based config screen and live-scrolling results view; no CLI arguments required to launch
12
+
-`make tui` Makefile target for one-command TUI launch
13
+
-`internal/scan` package: extracted scan engine (`scan.Run`) with typed `Event` channel, usable by both CLI and TUI
14
+
- TUI session persistence: last-used form values written to `~/.config/subenum/last.json` and restored on next launch or after pressing `r` (new scan)
15
+
16
+
### Changed
17
+
- CLI scan loop in `main.go` now delegates to `scan.Run()` instead of containing the worker pool inline
18
+
- External dependencies added: `github.com/charmbracelet/bubbletea` and `github.com/charmbracelet/bubbles` (TUI only; CLI path has zero external dependencies)
19
+
- TUI form field order: Simulate toggle promoted to field 3 (was field 8); Hit Rate row is hidden when Simulate is OFF
20
+
- TUI now shows a blinking cursor inside the active text input
21
+
- Pressing `r` on the scan results screen returns to the form with last-used values pre-filled (was reset to defaults)
* The function returns `true` if `LookupHost` returns no error (i.e., the domain resolved), and `false` otherwise.
76
81
***Interactions**: Workers call `dns.ResolveDomainWithRetry`, which delegates to `dns.ResolveDomain` with retry logic. It takes a fully qualified domain name, timeout duration, DNS server address, verbose flag, and retry count as input. It outputs a boolean indicating whether the domain resolved successfully. The result is used to decide if the domain should be printed to the console and/or written to the output file.
77
82
78
-
### 2.4. Concurrency Management (Worker Pool)
83
+
### 2.4. Concurrency Management (`internal/scan`)
79
84
80
85
***Purpose**: To efficiently perform DNS lookups for a large number of potential subdomains, `subenum` employs a worker pool pattern. This allows multiple DNS queries to be in flight concurrently, significantly speeding up the enumeration process compared to sequential lookups.
81
-
***Implementation**:
82
-
***`subdomains := make(chan string)`**: A buffered channel (though currently unbuffered in `main.go`, could be buffered for performance tuning) is created to act as a work queue. Subdomain prefixes read from the wordlist are sent to this channel.
83
-
***`var wg sync.WaitGroup`**: A `sync.WaitGroup` is used to wait for all worker goroutines to complete their tasks before the main function exits.
84
-
***Worker Goroutines Loop (`for i := 0; i < *concurrency; i++`)**: A loop launches a number of goroutines specified by the `-t` (concurrency) flag. Each goroutine acts as a worker.
85
-
*`wg.Add(1)`: Increments the `WaitGroup` counter for each worker started.
86
-
*`go func() { ... }()`: Each worker runs in its own goroutine.
87
-
*`defer wg.Done()`: Decrements the `WaitGroup` counter when the goroutine exits.
88
-
*`for subdomainPrefix := range subdomains { ... }`: Each worker continuously reads subdomain prefixes from the `subdomains` channel until the channel is closed. For each prefix, it constructs the full domain and calls `dns.ResolveDomainWithRetry()`.
89
-
***Closing the Channel (`close(subdomains)`)**: After all subdomain prefixes from the wordlist have been sent to the `subdomains` channel, the channel is closed. This signals to the worker goroutines that no more work will be added.
90
-
***Waiting for Completion (`wg.Wait()`)**: The main goroutine blocks until all worker goroutines have called `wg.Done()`, ensuring all lookups are finished.
91
-
***Interactions**: This component orchestrates the parallel execution of DNS lookups. It receives subdomain prefixes from the Wordlist Processing component (via the `subdomains` channel) and utilizes the DNS Resolution Engine within each worker goroutine. The number of workers is controlled by the Argument Parsing component.
86
+
***Implementation**: The worker pool logic lives in `internal/scan/runner.go` as `scan.Run(ctx, cfg, events)`. Both the CLI (`run()` in `main.go`) and the TUI (`internal/tui`) call this function.
87
+
***`scan.Config`**: A struct carrying all scan parameters (domain, entries slice, concurrency, timeout, DNS server, simulate flag, etc.).
88
+
***`scan.Event` / `scan.EventKind`**: Typed events emitted on a `chan<- scan.Event` — `EventResult`, `EventProgress`, `EventWildcard`, `EventError`, `EventDone`.
89
+
***`subdomains := make(chan string)`**: An internal channel acts as a work queue. Entries from the pre-loaded wordlist slice are fed into it.
90
+
***`var wg sync.WaitGroup`**: A `sync.WaitGroup` waits for all worker goroutines to finish.
91
+
***Worker Goroutines Loop**: `cfg.Concurrency` goroutines are launched. Each reads prefixes from the channel, constructs the full domain, and calls `dns.ResolveDomainWithRetry()` (or `dns.SimulateResolution()` in simulate mode).
92
+
***Progress ticker**: A separate goroutine fires every second and emits `EventProgress` events so callers can update their display.
93
+
***Closing the Channel**: After all entries are sent, the channel is closed, signalling workers to exit. `wg.Wait()` blocks until all workers are done, then `EventDone` is emitted.
94
+
***Interactions**: `scan.Run` is the single entry point for scanning used by both the CLI output pipeline and the Bubble Tea TUI. It decouples the scan engine from any specific display layer.
* Shows percentage completion, processed count, and found count
120
123
***Interactions**: The Progress Monitoring component works alongside the worker goroutines, using atomic operations to safely track counts across multiple goroutines. Writing to stderr keeps stdout pipe-clean.
***Purpose**: Remember the last-used TUI form values across sessions so users don't have to re-type domain, wordlist path, and scan parameters every time.
128
+
***Implementation**:
129
+
*`savedConfig` struct mirrors `formValues` with JSON tags.
130
+
*`configPath()` — returns `os.UserConfigDir()/subenum/last.json` (e.g. `~/.config/subenum/last.json` on Linux/macOS, `%AppData%\subenum\last.json` on Windows).
131
+
*`saveConfig(fv formValues) error` — marshals `formValues` to JSON and writes it atomically with `os.WriteFile`. Called in `beginScan()` immediately before launching the scan goroutine. Errors are silently discarded so a write failure never blocks the scan.
132
+
*`loadSavedConfig() (savedConfig, bool)` — reads and unmarshals the file. Returns `false` if the file doesn't exist or is unreadable, causing `newFormModel` to fall back to hardcoded defaults.
133
+
***Interactions**: `tui.New()` calls `loadSavedConfig()` on startup and passes the result to `newFormModel`. The `r` keybind (new scan) also calls `loadSavedConfig()` so the form is pre-filled with the values from the scan that just completed.
134
+
122
135
## 3. Data Flow
123
136
124
137
The flow of data through the `subenum` application can be summarized as follows:
@@ -151,7 +164,7 @@ The flow of data through the `subenum` application can be summarized as follows:
1. Evaluate whether it's truly necessary or if the functionality can be implemented using the standard library.
232
251
2. If a dependency is needed, add it with:
@@ -239,6 +258,7 @@ Please follow these style guidelines when contributing:
239
258
240
259
Areas for potential enhancement include:
241
260
261
+
* **Terminal UI**: An interactive TUI (`-tui` flag) built with Bubble Tea. Provides a form-based config screen and a live-scrolling results view — no arguments required to launch. Last-used values persist to `~/.config/subenum/last.json` across sessions.
242
262
* **Output Formats**: Supporting different output formats (JSON, CSV) in addition to the current plain text output file (`-o`).
243
263
* **Result Filtering**: Allowing users to filter results based on DNS record types.
244
264
* **Recursive Enumeration**: Adding support for recursive subdomain enumeration (e.g., finding subdomains of discovered subdomains).
0 commit comments