Conversation
* parse Darwin routing table with golang.org/x/net/route * add subnet mask to destination * add LinkAddr support * consolidate darwin and freebsd routing logic into one file * use windows api to get routing table instead of os.exec * fix potential nil pointer dereference * fixing minor issues --------- Co-authored-by: Mzack9999 <mzack9999@protonmail.com>
* fix: add system DNS fallback for split‑DNS/VPN resolution * make system resolver optional --------- Co-authored-by: Mzack9999 <mzack9999@protonmail.com>
* fix: scan type inconsistency after scanner initialization If SYN scan type is specified for the runner, but NewScanner failed to acquire a listen handler, NewScanner will fallback to CONNECT scan without updating the runner's scan type option. This causes the runner to later attempt to use raw packet scan with an empty listen handler when it should have used a normal connect scan. * minor fix --------- Co-authored-by: Mzack9999 <mzack9999@protonmail.com>
WalkthroughA new fingerprint package implements nmap-style service version detection with parallel TCP/UDP probing, regex-based match evaluation, and probe database parsing. Integration into the scanner pipeline discovers and enriches service metadata on identified ports. Routing and prediction model enhancements support the feature. Changes
Sequence DiagramsequenceDiagram
participant Scanner as Scanner
participant Runner as Runner
participant Engine as Fingerprint Engine
participant ProbeDB as ProbeDB
participant TCPTarget as TCP Target
participant ResultMap as Result Map
Scanner->>Runner: StartScan (ServiceVersion enabled)
Runner->>Runner: initServiceDetection()
Runner->>ProbeDB: LocateNmapProbes() & ParseProbeFile()
ProbeDB-->>Runner: Loaded ServiceProbes
Runner->>Engine: New(db, options)
Engine-->>Runner: Engine ready
Scanner->>Runner: onReceive(port discovered)
Runner->>Runner: Push to fingerprint channel
par Scanning continues
Runner->>Engine: FingerprintStream(targets channel)
Engine->>Engine: For each target: TCP/UDP probing
Engine->>TCPTarget: Dial + write probe
TCPTarget-->>Engine: Response
Engine->>Engine: tryMatches(response vs ProbeDB)
Engine-->>Runner: StreamResult (detected service)
and
Scanner->>Scanner: Continue port scanning
end
Runner->>ResultMap: Store Service by ip:port
Scanner->>Runner: Scan complete
Runner->>Runner: waitServiceDetection()
Runner->>ResultMap: Merge services into scanResults
Runner->>Runner: handleOutput (enriched with services)
Runner-->>Scanner: Output with CPEs & service metadata
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (8)
pkg/port/port.go (1)
23-31: LGTM - Minor optimization suggestion.The refactor to use
fmt.Fprintfdirectly is cleaner. However, line 27 usesfmt.Fprintffor a constant string"/tls"which has unnecessary formatting overhead. Consider usingbuilder.WriteString("/tls")instead.💡 Optional micro-optimization
func (p *Port) StringWithDetails() string { var builder strings.Builder _, _ = fmt.Fprintf(&builder, "%d[%s", p.Port, p.Protocol.String()) if p.TLS { - _, _ = fmt.Fprintf(&builder, "/tls") + _, _ = builder.WriteString("/tls") } - _, _ = fmt.Fprintf(&builder, "]") + _, _ = builder.WriteString("]") return builder.String() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/port/port.go` around lines 23 - 31, In Port.StringWithDetails(), avoid using fmt.Fprintf to write the constant "/tls" (unnecessary formatting overhead); replace the fmt.Fprintf(&builder, "/tls") call with builder.WriteString("/tls") inside the TLS branch so the rest of the function (fmt.Fprintf for the formatted pieces) remains unchanged.pkg/routing/router_windows.go (1)
24-34: No fallback to outbound IP detection on Windows.Unlike the BSD implementation which falls back to
fallbackOutboundRoutes()when both native and netstat methods fail, Windows returns an error directly. This could cause initialization failures on systems where routing table access is restricted but outbound connectivity works.Consider adding a similar fallback for consistency:
💡 Optional: Add outbound IP fallback
func New() (Router, error) { routes, err := fetchRoutesNative() if err != nil { gologger.Debug().Msgf("native Windows routing API failed, falling back to netsh: %v", err) routes, err = fetchRoutesNetsh() } if err != nil { - return nil, err + gologger.Debug().Msgf("netsh fallback failed, falling back to outbound IPs: %v", err) + return fallbackOutboundRoutes() } return &baseRouter{Routes: routes}, nil }Note:
fallbackOutboundRoutes()is currently only defined inrouter_bsd.go. You'd need to extract it torouter.goor duplicate it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/routing/router_windows.go` around lines 24 - 34, The Windows New() should mirror BSD's behavior by attempting fallbackOutboundRoutes() if both fetchRoutesNative() and fetchRoutesNetsh() fail: modify New() to call fetchRoutesNative(), then on error try fetchRoutesNetsh(), and if that also errors, call fallbackOutboundRoutes() and use its returned routes; ensure the function references fetchRoutesNative, fetchRoutesNetsh, fallbackOutboundRoutes, and baseRouter; if fallbackOutboundRoutes is currently only in router_bsd.go, either move it into a shared file (router.go) or duplicate it into the Windows build so the fallback is available.pkg/fingerprint/locate.go (1)
41-88: Consider adding debug logging for probe file discovery.When
LocateNmapProbes()returns an empty string, there's no visibility into which paths were checked. This could make troubleshooting difficult for users.💡 Optional: Add debug logging
func nmapProbePaths() []string { var paths []string + // Note: Paths are checked in order by LocateNmapProbes() switch runtime.GOOS { // ... existing code ... } return paths }Or add logging in
LocateNmapProbes()when no file is found to list searched paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/fingerprint/locate.go` around lines 41 - 88, LocateNmapProbes() currently returns an empty string with no visibility into what paths were searched; use nmapProbePaths() to gather the candidate list and log it when no probe file is found. Modify LocateNmapProbes() so that after iterating candidates (via nmapProbePaths()) and before returning "", it emits a debug or info log containing the slice of paths (and optionally which OS/ENV values influenced them), e.g., call the existing logger with a message like "nmap probe not found, searched paths: %v" and include nmapProbePaths() output to aid troubleshooting.pkg/routing/router_test.go (2)
345-358: Unused variablesrcIPin test.Line 357 has
_ = srcIPto silence the compiler, butsrcIP(defined at line 346) is never used meaningfully in the test. Either use it in an assertion or remove it.func TestBaseRouter_RouteWithSrc_HWAddrMatch(t *testing.T) { - srcIP := net.ParseIP("10.0.0.5") routes := []*Route{ {Type: IPv4, Destination: "10.0.0.0/8", NetworkInterface: eth0, Gateway: "10.0.0.1"}, {Type: IPv4, Destination: "192.168.0.0/16", NetworkInterface: eth1, Gateway: "192.168.0.1"}, } router := &baseRouter{Routes: routes} iface, _, _, err := router.RouteWithSrc(eth1.HardwareAddr, nil, net.ParseIP("8.8.8.8")) require.NoError(t, err) assert.Equal(t, eth1, iface) - - _ = srcIP }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/routing/router_test.go` around lines 345 - 358, The test TestBaseRouter_RouteWithSrc_HWAddrMatch defines srcIP but never uses it; remove the unused variable and the no-op `_ = srcIP` (or alternatively use srcIP in the RouteWithSrc call if the intention was to test source IP handling). Specifically, edit the test in the baseRouter tests around the RouteWithSrc invocation (function: RouteWithSrc on type baseRouter) to either pass srcIP into RouteWithSrc or delete the srcIP declaration and the `_ = srcIP` line so the test has no unused variables.
454-495: Unused variables in integration test.Lines 493-494 have
_ = routerand_ = srcIPbut neither is used in the test. The sub-tests callFindRouteForIpdirectly instead of using therouterinstance.💡 Suggested fix: Use router instance or remove
func TestBaseRouter_RealisticRouteTable(t *testing.T) { - srcIP := net.ParseIP("192.168.1.50") - routes := []*Route{ // ... } router := &baseRouter{Routes: routes} t.Run("LAN address uses /24", func(t *testing.T) { - route, err := FindRouteForIp(net.ParseIP("192.168.1.100"), routes) + _, _, _, err := router.Route(net.ParseIP("192.168.1.100")) require.NoError(t, err) - assert.Equal(t, eth0, route.NetworkInterface) - assert.Empty(t, route.Gateway) + // ... adjust assertions for Route() return values }) // ... similar changes for other sub-tests - - _ = router - _ = srcIP }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/routing/router_test.go` around lines 454 - 495, The test declares router and srcIP but never uses them; either remove the unused variables or adjust sub-tests to exercise the baseRouter instance and srcIP. Specifically, either delete the lines assigning _ = router and _ = srcIP, or replace direct calls to FindRouteForIp with calls through the router (e.g., call router.FindRouteForIp or a method on baseRouter that wraps FindRouteForIp) and use srcIP where appropriate (for example, use srcIP as the source address in any call that needs it). Ensure references to baseRouter, router, srcIP and FindRouteForIp are updated consistently so no unused variables remain.pkg/fingerprint/options.go (1)
36-41: Consider validating nil dialer to prevent potential nil pointer dereference.
WithDialeraccepts anyDialFuncincluding nil. If called with nil,e.dialerwould be set to nil, potentially causing a nil pointer dereference during TCP connections. While current callers inrunner.goconstruct valid dialers, a defensive nil check would prevent future misuse.🛡️ Optional defensive fix
func WithDialer(d DialFunc) Option { return func(e *Engine) { + if d != nil { e.dialer = d + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/fingerprint/options.go` around lines 36 - 41, The WithDialer Option should defensively handle a nil DialFunc to avoid setting Engine.dialer to nil; update the WithDialer function to check if d == nil and if so return a no-op Option (leave e.dialer unchanged) otherwise set e.dialer = d. This change touches the WithDialer function and the Engine.dialer field so callers like runner.go remain unaffected but future misuse is prevented.pkg/runner/runner_test.go (1)
991-1023: Test relies on global state mutation—ensure tests remain sequential.This test mutates package-level variables (
scan.PkgRouter,privileges.IsPrivileged,scan.ListenHandlers) without synchronization. While thedeferrestores correctly and tests aren't currently parallel, this pattern will cause data races ift.Parallel()is ever added to these tests.Consider documenting this constraint or adding a comment to prevent accidental parallelization.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/runner/runner_test.go` around lines 991 - 1023, This test mutates package-level globals (scan.PkgRouter, privileges.IsPrivileged, scan.ListenHandlers) which can cause data races if run in parallel; update TestNewRunner_ScanTypeSyncAfterFallback by adding a clear comment above the test stating it intentionally mutates global state and must not be run with t.Parallel(), and either (preferred) protect the mutated globals with a package-level test mutex or (alternatively) document the sequential requirement in the test comment so future maintainers don't add t.Parallel(); reference the symbols scan.PkgRouter, privileges.IsPrivileged, scan.ListenHandlers, and the test name TestNewRunner_ScanTypeSyncAfterFallback when adding the comment or mutex.pkg/fingerprint/bench_test.go (1)
353-357: Avoid a hard wall-clock threshold in this test.The
<2sassertion depends on machine load and scheduler noise, so it can fail spuriously even when probe parallelism is working. This is safer as a benchmark or an opt-in integration test.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/fingerprint/bench_test.go` around lines 353 - 357, The test currently fails on noisy machines because it asserts elapsed < 2*time.Second; change it to avoid a hard wall-clock check by either (a) removing the absolute threshold and instead comparing the parallel run against a measured sequential run (compute sequentialElapsed and assert elapsed < sequentialElapsed - some margin) or (b) make the timing assertion opt-in/configurable (use testing.Short or an env var like TEST_TIMING_THRESHOLD) and otherwise only t.Logf the elapsed time; locate the assertion referencing elapsed and the GetRequest/NULL probe comment in bench_test.go to implement the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pkg/fingerprint/engine_test.go`:
- Around line 197-203: The test currently only logs when an unexpected result is
present for key (fmt.Sprintf("%s:%d", host, port)) but should fail; update the
fast-mode assertion in engine_test.go so that if results[key] exists (indicating
the NULL probe or a non-fast probe ran and GetRequest executed), the test calls
t.Fatalf (or t.Errorf followed by t.FailNow) with a clear message that a result
was produced in fast mode, referencing key and that GetRequest should not have
run; modify the block checking results[key] to fail the test instead of t.Log.
In `@pkg/fingerprint/engine.go`:
- Around line 464-471: The current logic in the fingerprinting read sequence
(use of remaining, conn.SetReadDeadline and the single extra conn.Read call)
stops after at most one 200ms poll and may truncate multi-chunk responses;
change it to loop reads until the overall waitTimeout has elapsed or no more
data arrives: repeatedly set a short read deadline (e.g. 200ms), call conn.Read
and append to response while n>0, break only when a read times out/no data or
the remaining time is exhausted, and then return response and false; update the
code around the existing conn.SetReadDeadline/conn.Read usage so it continually
polls rather than returning after a single follow-up read.
- Around line 157-160: Engine.fingerprintOne currently ignores
ProbeDB.ExcludeTCP and ProbeDB.ExcludeUDP populated by ParseProbes, so excluded
ports are still dialed; update Engine.fingerprintOne to consult e.db.ExcludeTCP
and e.db.ExcludeUDP (or the corresponding exclude sets on the loaded ProbeDB)
before attempting any dial and skip fingerprinting for ports present in those
sets, and apply the same check to the other fingerprinting path referenced in
the review (the dialing loop around lines 684-688) so both code paths honor
ProbeDB.ExcludeTCP/ExcludeUDP.
- Around line 720-722: The UDP branch currently only calls
e.tryMatches(sp.Matches) and thus skips softmatch and fallback probe rules;
update the UDP matching logic so it evaluates soft matches and fallback probes
the same way TCP does: after checking sp.Matches call into the same softmatch
and fallback evaluation flow (e.g., call whatever helper(s) that handle
sp.SoftMatches and fallback probe rules or merge sp.Matches + sp.SoftMatches +
any fallback match set before invoking e.tryMatches), and ensure the final
return still uses matchResultToResult(result, false, "") so UDP results include
softmatch/fallback outcomes just like the TCP path.
- Around line 127-133: The send to results after calling e.fingerprintOne can
block indefinitely; modify the worker goroutine that calls e.fingerprintOne(ctx,
t) (and currently sends StreamResult with r.ToService()) to use a select that
attempts to send the StreamResult to the results channel or returns if
ctx.Done() is closed, and ensure wg.Done() is always executed (preferably via
defer wg.Done() at the start of the goroutine) so the closer goroutine's
wg.Wait() cannot hang; in short, guard the results <- send with a select { case
results<-sr: case <-ctx.Done(): } and guarantee wg.Done() runs even on cancel.
In `@pkg/fingerprint/fingerprint.go`:
- Around line 34-44: The ToService() method on Result is missing mapping for the
Result.DeviceType into port.Service.DeviceType; update the ToService() return
struct (in func (r *Result) ToService()) to set DeviceType: r.DeviceType so the
Result's DeviceType is propagated into the created *port.Service object.
In `@pkg/fingerprint/parser.go`:
- Around line 203-216: The code currently swallows parseMatch errors in the
parse loop (inside ParseProbes) by continuing, causing signatures to be silently
dropped; instead, have the loop propagate the error (do not continue). Replace
the `continue` in both the "match " and "softmatch " branches so that parseMatch
errors are returned (wrap with context including the offending line and whether
it was a match or softmatch) from the enclosing ParseProbes function (or
otherwise bubble the error to the caller), referencing parseMatch, ParseProbes,
current.Matches and current.SoftMatches so failures are visible to callers
rather than silently ignored.
- Around line 545-553: substituteGroups currently replaces capture placeholders
in ascending order which causes "$1" to be substituted inside "$10" etc.; change
the replacement loop in substituteGroups to iterate from the highest capture
index down to 1 (e.g., start at min(len(submatches)-1, 9) and decrement) so that
multi-digit placeholders like "$10" are replaced before "$1"; update the loop in
substituteGroups accordingly using the same strconv.Itoa and strings.ReplaceAll
calls but with descending indices.
- Around line 290-335: The decodeNmapEscapes function currently only decodes a
small whitelist and leaves unknown backslashes in the output (e.g., "\|"), so
update decodeNmapEscapes to treat any unrecognized escape as "literal next byte"
by appending s[i+1] and advancing i by 2 in the switch default branch; also
handle a trailing lone backslash at end of string by appending '\\' and
advancing to terminate. Make this change in the decodeNmapEscapes function to
ensure probes that escape delimiters are decoded correctly.
In `@pkg/routing/router_bsd.go`:
- Around line 246-260: The current IPv6 fallback creates a Route when ip6 is
nil, ignores errors from FindInterfaceByIp and assigns an IPv4-only interface
without a DefaultSourceIP which breaks baseRouter.Route()/FindSourceIpForIp; fix
by removing the blind fallback or by checking the candidate interface for IPv6
addresses before appending: call FindInterfaceByIp only when ip6 != nil and
propagate/handle its error instead of discarding it, and if you must reuse
routes[0].NetworkInterface ensure that interface actually has an IPv6 address
(or set DefaultSourceIP to a valid IPv6) before creating the Route struct
(update the code around ip6, FindInterfaceByIp, Route creation and the fallback
branch accordingly).
In `@pkg/routing/router.go`:
- Around line 63-71: baseRouter.Route currently returns (nil, nil,
route.DefaultSourceIP, nil) when route.DefaultSourceIP is set, causing callers
to get a nil *net.Interface even if the route contains NetworkInterface/Gateway
info; change the early return to return the route's NetworkInterface and Gateway
when present (i.e., return route.NetworkInterface, route.Gateway,
route.DefaultSourceIP, nil) so callers that need iface (e.g., for raw packet
sending) receive it; update the logic in Route (function baseRouter.Route) to
prefer route.NetworkInterface and route.Gateway when route.DefaultSourceIP !=
nil, falling back to nils only if those fields are actually absent.
In `@pkg/runner/healthcheck.go`:
- Line 78: The healthcheck output prints the wrong UDP endpoint (it writes
"scanme.sh:80" while the probe actually dials "scanme.sh:53"); update the
fmt.Fprintf call that writes to test (referenced by testResult and the
fmt.Fprintf call) to report the actual UDP IPv4 endpoint used by the probe (use
the same endpoint/variable used when dialing, e.g., the "scanme.sh:53" value or
the endpoint variable) so the diagnostic message matches the probe target.
In `@pkg/runner/output.go`:
- Around line 50-52: CSVFields currently uses fmt.Sprint which renders the CPEs
[]string as Go's bracketed slice (e.g. "[a b]") making CSV round-tripping
fragile; update the CSVFields implementation to explicitly serialize the CPEs
field (named CPEs on the output struct) into a single string (e.g.,
strings.Join(o.CPEs, ";") or JSON-encode) before returning it for the CSV column
so each CPE is reliably delimited and parseable; locate the CSVFields method
(around lines 161-168) and replace the fmt.Sprint usage for the CPEs case with
an explicit join/encoding step.
In `@pkg/runner/runner.go`:
- Around line 1120-1124: The log reads targetCount too early because the
goroutine that feeds targets increments targetCount concurrently, so move the
counting step before starting that goroutine: compute the total targets (e.g.,
len(targets) or pre-iterate channels/slices to get the count) and assign
targetCount prior to launching the feeding goroutine, then call
gologger.Info().Msgf using that count (or alternatively defer the log until
after the feeder has finished counting); reference variables: targetCount and
r.options.ServiceVersionWorkers and the existing feeder goroutine that populates
targets so you update where the log call and goroutine are ordered.
In `@pkg/scan/scan.go`:
- Around line 182-191: The loop presently downgrades options.ScanType from
TypeSyn to TypeConnect when Acquire fails, which silently flips SYN-only
discovery to CONNECT; instead, when Acquire returns an error and
options.ScanType == TypeSyn, stop downgrading and return or propagate the
acquireErr so the caller fails fast. Modify the loop in pkg/scan/scan.go (the
Acquire(...) call and handling around options.ScanType, TypeSyn, TypeConnect and
scanner.ListenHandler) to remove the automatic assignment options.ScanType =
TypeConnect and the continue path for the SYN case; if Acquire fails while
ScanType is TypeSyn, return acquireErr (or wrap and return) so the caller
enforces the SYN-only policy established in pkg/runner/validate.go.
In `@README.md`:
- Around line 103-104: Update the README help block to document the new native
fingerprinting CLI flags by adding entries for -sD, -sV, -sV-fast, -sV-timeout,
-sV-workers, and -sV-probes alongside the existing options; reference the same
help section that currently lists -dns-order and -sr/-system-resolver and add
concise descriptions and default values for each new flag (e.g., -sD to enable
service detection, -sV to enable version fingerprinting, -sV-fast to use a
reduced probe set, -sV-timeout to set probe timeout, -sV-workers to control
concurrency, -sV-probes to select probe profile) so the README usage output
reflects the new native fingerprinting feature instead of only showing the
deprecated -nmap option.
---
Nitpick comments:
In `@pkg/fingerprint/bench_test.go`:
- Around line 353-357: The test currently fails on noisy machines because it
asserts elapsed < 2*time.Second; change it to avoid a hard wall-clock check by
either (a) removing the absolute threshold and instead comparing the parallel
run against a measured sequential run (compute sequentialElapsed and assert
elapsed < sequentialElapsed - some margin) or (b) make the timing assertion
opt-in/configurable (use testing.Short or an env var like TEST_TIMING_THRESHOLD)
and otherwise only t.Logf the elapsed time; locate the assertion referencing
elapsed and the GetRequest/NULL probe comment in bench_test.go to implement the
change.
In `@pkg/fingerprint/locate.go`:
- Around line 41-88: LocateNmapProbes() currently returns an empty string with
no visibility into what paths were searched; use nmapProbePaths() to gather the
candidate list and log it when no probe file is found. Modify LocateNmapProbes()
so that after iterating candidates (via nmapProbePaths()) and before returning
"", it emits a debug or info log containing the slice of paths (and optionally
which OS/ENV values influenced them), e.g., call the existing logger with a
message like "nmap probe not found, searched paths: %v" and include
nmapProbePaths() output to aid troubleshooting.
In `@pkg/fingerprint/options.go`:
- Around line 36-41: The WithDialer Option should defensively handle a nil
DialFunc to avoid setting Engine.dialer to nil; update the WithDialer function
to check if d == nil and if so return a no-op Option (leave e.dialer unchanged)
otherwise set e.dialer = d. This change touches the WithDialer function and the
Engine.dialer field so callers like runner.go remain unaffected but future
misuse is prevented.
In `@pkg/port/port.go`:
- Around line 23-31: In Port.StringWithDetails(), avoid using fmt.Fprintf to
write the constant "/tls" (unnecessary formatting overhead); replace the
fmt.Fprintf(&builder, "/tls") call with builder.WriteString("/tls") inside the
TLS branch so the rest of the function (fmt.Fprintf for the formatted pieces)
remains unchanged.
In `@pkg/routing/router_test.go`:
- Around line 345-358: The test TestBaseRouter_RouteWithSrc_HWAddrMatch defines
srcIP but never uses it; remove the unused variable and the no-op `_ = srcIP`
(or alternatively use srcIP in the RouteWithSrc call if the intention was to
test source IP handling). Specifically, edit the test in the baseRouter tests
around the RouteWithSrc invocation (function: RouteWithSrc on type baseRouter)
to either pass srcIP into RouteWithSrc or delete the srcIP declaration and the
`_ = srcIP` line so the test has no unused variables.
- Around line 454-495: The test declares router and srcIP but never uses them;
either remove the unused variables or adjust sub-tests to exercise the
baseRouter instance and srcIP. Specifically, either delete the lines assigning _
= router and _ = srcIP, or replace direct calls to FindRouteForIp with calls
through the router (e.g., call router.FindRouteForIp or a method on baseRouter
that wraps FindRouteForIp) and use srcIP where appropriate (for example, use
srcIP as the source address in any call that needs it). Ensure references to
baseRouter, router, srcIP and FindRouteForIp are updated consistently so no
unused variables remain.
In `@pkg/routing/router_windows.go`:
- Around line 24-34: The Windows New() should mirror BSD's behavior by
attempting fallbackOutboundRoutes() if both fetchRoutesNative() and
fetchRoutesNetsh() fail: modify New() to call fetchRoutesNative(), then on error
try fetchRoutesNetsh(), and if that also errors, call fallbackOutboundRoutes()
and use its returned routes; ensure the function references fetchRoutesNative,
fetchRoutesNetsh, fallbackOutboundRoutes, and baseRouter; if
fallbackOutboundRoutes is currently only in router_bsd.go, either move it into a
shared file (router.go) or duplicate it into the Windows build so the fallback
is available.
In `@pkg/runner/runner_test.go`:
- Around line 991-1023: This test mutates package-level globals (scan.PkgRouter,
privileges.IsPrivileged, scan.ListenHandlers) which can cause data races if run
in parallel; update TestNewRunner_ScanTypeSyncAfterFallback by adding a clear
comment above the test stating it intentionally mutates global state and must
not be run with t.Parallel(), and either (preferred) protect the mutated globals
with a package-level test mutex or (alternatively) document the sequential
requirement in the test comment so future maintainers don't add t.Parallel();
reference the symbols scan.PkgRouter, privileges.IsPrivileged,
scan.ListenHandlers, and the test name TestNewRunner_ScanTypeSyncAfterFallback
when adding the comment or mutex.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5daf89a4-5dfb-4b60-beb2-7f0db7980eb1
📒 Files selected for processing (34)
.gitignoreREADME.mdgo.modpkg/fingerprint/bench_test.gopkg/fingerprint/engine.gopkg/fingerprint/engine_test.gopkg/fingerprint/fingerprint.gopkg/fingerprint/locate.gopkg/fingerprint/nmap_comparison_test.gopkg/fingerprint/options.gopkg/fingerprint/parser.gopkg/fingerprint/parser_test.gopkg/port/port.gopkg/routing/router.gopkg/routing/router_bsd.gopkg/routing/router_darwin.gopkg/routing/router_freebsd.gopkg/routing/router_test.gopkg/routing/router_windows.gopkg/runner/default.gopkg/runner/healthcheck.gopkg/runner/options.gopkg/runner/output.gopkg/runner/output_test.gopkg/runner/runner.gopkg/runner/runner_test.gopkg/runner/util.gopkg/runner/util_test.gopkg/runner/validate.gopkg/runner/validate_test.gopkg/scan/option.gopkg/scan/scan.gopkg/scan/scan_common.gopkg/scan/scan_type_test.go
💤 Files with no reviewable changes (2)
- pkg/routing/router_darwin.go
- pkg/routing/router_freebsd.go
Neo - PR Security ReviewNo security issues found Comment |
Resolve conflicts in go.mod (take newer dependency versions from dev) and pkg/scan/scan_type_test.go (trivial comment style).
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (1)
pkg/fingerprint/parser.go (1)
205-218:⚠️ Potential issue | 🟠 MajorStop silently dropping invalid signatures.
These branches still log and continue on
parseMatcherrors, so a malformedmatchorsoftmatchline yields a “successful” probe load with fewer fingerprints than the file actually defines.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/fingerprint/parser.go` around lines 205 - 218, The code currently logs and continues when parseMatch fails in the "match " and "softmatch " branches, causing malformed signatures to be silently dropped; change both branches to propagate the parse error instead of skipping: when parseMatch(line[6:]) or parseMatch(line[10:]) returns err, wrap or annotate the error with context (including current.Name and whether it was match/softmatch) and return that error from the surrounding parser function so probe loading fails; update any callers if needed to handle the returned error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pkg/fingerprint/engine.go`:
- Around line 209-214: The current loop short-circuits on any r.hard from
channel ch (in the hinted parallel phase), causing NULL-probe "tcpwrapped"
closes to be treated as definitive; change the logic in the loop that consumes
ch so it only cancels probes and returns immediately when r.hard is a real hard
match that is NOT the tcpwrapped/null-close heuristic (i.e., detect the
tcpwrapped flag/state on r.hard and skip short-circuiting for it), otherwise
continue waiting for other hinted probe results (so GetRequest and other probes
can win); apply the same change to the other identical block around the
GetRequest area (the 395-400 occurrence) and ensure probeCancel() is only called
when you truly accept a non-tcpwrapped hard match before calling
matchResultToResult.
In `@pkg/result/results.go`:
- Around line 233-241: The loop over r.ipPorts should deduplicate numeric ports
per host before applying the "at least 2 ports" filter and before exporting;
change the logic in the block that iterates r.ipPorts/ports (the variable ports
and elements p with p.Port and p.String()) to build a set (map[int]struct{}) of
unique p.Port values, check if the set size is < 2 and continue if so, then
convert the set keys into the portList slice (optionally sorted) and assign
out[ip] = portList so duplicate numeric ports (e.g., 53/tcp and 53/udp) are not
counted twice.
In `@pkg/routing/router_bsd.go`:
- Around line 255-266: The fallback IPv6 route creation appends any IPv6 addr
(from iface.Addrs()) into Route.DefaultSourceIP, which can include link-local
addresses; change the loop in router_bsd.go that builds the IPv6 fallback so it
skips IPs where ipNet.IP.IsLinkLocalUnicast() is true and only appends a
Route{Type: IPv6, Default: true, DefaultSourceIP: ipNet.IP, NetworkInterface:
iface} when the address is not link-local, matching the source selection logic
used elsewhere (see router.go's !ipAddress.IsLinkLocalUnicast() check) and
preventing non-routable link-local addresses from being used as DefaultSourceIP.
In `@pkg/runner/runner.go`:
- Around line 1254-1270: The enriched service/version/CND text is only applied
to stdout in the r.options.ServiceVersion branch, but file output still goes
through WriteHostOutput and loses that info; extract the plain-text enrichment
logic (the code that builds hostPort, appends formatServiceInfo(p.Service) and
cdnName when r.options.OutputCDN && isCDNIP) into a reusable helper (e.g.,
formatEnrichedHostPort or FormatHostOutput) and call it from both the stdout
loop and from WriteHostOutput (or ensure WriteHostOutput invokes that helper
when r.options.ServiceVersion is set) so that -sV combined with -o produces the
same enriched lines in files as on the terminal.
- Around line 1134-1140: The workers for FingerprintStream are created with
context.WithCancel(context.Background()), which detaches them from the
RunEnumeration lifecycle and can leave goroutines blocked on r.fpTargetCh;
change the created context to derive from the RunEnumeration context (use the
existing ctx passed into RunEnumeration or r.ctx) by replacing
context.WithCancel(context.Background()) with context.WithCancel(ctx) so that
r.fpCancel, r.fpTargetCh, r.fpDone and the FingerprintStream call
(engine.FingerprintStream) are tied to the scan context and will be cancelled
when the parent context is done.
- Around line 258-276: The send to r.fpTargetCh inside the OnReceive path can
block and also panic if waitServiceDetection closes the channel concurrently;
modify the send so it never blocks and cannot crash: wrap the send of
fingerprint.Target (constructed from hostname, hostResult.IP and p.Port/TLS
fields) in a small helper that does a non-blocking select (case r.fpTargetCh <-
t: default:) and protects against a closed-channel panic with a defer recover(),
so late sends are safely dropped instead of blocking or panicking.
In `@pkg/runner/smartscan.go`:
- Around line 126-128: The call to sender.flush() currently ignores any returned
error; instead, check the error returned from sender.flush() (in the same
function where this snippet appears), and do not swallow it—either return the
error up the call stack or wrap it with context and propagate it (or at minimum
log it as an error with sufficient context) so transport failures are visible;
update the code around sender.flush() to handle the error case rather than
assigning it to _.
- Around line 98-104: The current logic trains a transient model from
r.scanner.ScanResults.GetHostPortsMap() and then calls model.MergeNew(learned),
which only adds previously-missing pairs so an early sparse observation
permanently fixes probabilities; instead, recompute and apply fresh
probabilities on every retry by rebuilding the learned model from hostPorts each
time and replacing/updating the main model rather than using MergeNew.
Concretely, in the retry block where you call prediction.NewModel(),
learned.Train(hostPorts) and model.MergeNew(learned), change the merge step to
either replace the model’s probabilities with the newly trained learned model
(e.g., assign or call a full-merge/replace method) or use a merge method that
overwrites existing entries so subsequent retries can correct earlier estimates.
---
Duplicate comments:
In `@pkg/fingerprint/parser.go`:
- Around line 205-218: The code currently logs and continues when parseMatch
fails in the "match " and "softmatch " branches, causing malformed signatures to
be silently dropped; change both branches to propagate the parse error instead
of skipping: when parseMatch(line[6:]) or parseMatch(line[10:]) returns err,
wrap or annotate the error with context (including current.Name and whether it
was match/softmatch) and return that error from the surrounding parser function
so probe loading fails; update any callers if needed to handle the returned
error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d35706d7-9256-49f2-bf8b-a6bb5352b195
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (21)
.gitignoreREADME.mdgo.modpkg/fingerprint/bench_test.gopkg/fingerprint/engine.gopkg/fingerprint/engine_test.gopkg/fingerprint/fingerprint.gopkg/fingerprint/nmap_comparison_test.gopkg/fingerprint/parser.gopkg/prediction/default_model.gopkg/prediction/model.gopkg/result/results.gopkg/routing/router.gopkg/routing/router_bsd.gopkg/runner/healthcheck.gopkg/runner/options.gopkg/runner/output.gopkg/runner/runner.gopkg/runner/runner_test.gopkg/runner/smartscan.gopkg/runner/validate.go
✅ Files skipped from review due to trivial changes (4)
- .gitignore
- README.md
- pkg/fingerprint/engine_test.go
- pkg/fingerprint/bench_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
- go.mod
- pkg/runner/validate.go
- pkg/runner/options.go
- pkg/fingerprint/fingerprint.go
- pkg/runner/runner_test.go
- pkg/runner/healthcheck.go
- pkg/fingerprint/nmap_comparison_test.go
| for i := 0; i < len(hinted); i++ { | ||
| r := <-ch | ||
| if r.hard != nil { | ||
| probeCancel() | ||
| return matchResultToResult(r.hard, r.tls, "") | ||
| } |
There was a problem hiding this comment.
Don't let NULL's tcpwrapped heuristic short-circuit active probes.
In the parallel hinted phase, a quick close on the NULL probe is treated as a definitive hard match. That can mislabel services that reject idle connections but do respond once an active probe sends data, because tcpwrapped wins before GetRequest or the other hinted probes finish.
Also applies to: 395-400
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/fingerprint/engine.go` around lines 209 - 214, The current loop
short-circuits on any r.hard from channel ch (in the hinted parallel phase),
causing NULL-probe "tcpwrapped" closes to be treated as definitive; change the
logic in the loop that consumes ch so it only cancels probes and returns
immediately when r.hard is a real hard match that is NOT the
tcpwrapped/null-close heuristic (i.e., detect the tcpwrapped flag/state on
r.hard and skip short-circuiting for it), otherwise continue waiting for other
hinted probe results (so GetRequest and other probes can win); apply the same
change to the other identical block around the GetRequest area (the 395-400
occurrence) and ensure probeCancel() is only called when you truly accept a
non-tcpwrapped hard match before calling matchResultToResult.
| for ip, ports := range r.ipPorts { | ||
| if len(ports) < 2 { | ||
| continue | ||
| } | ||
| portList := make([]int, 0, len(ports)) | ||
| for _, p := range ports { | ||
| portList = append(portList, p.Port) | ||
| } | ||
| out[ip] = portList |
There was a problem hiding this comment.
Deduplicate numeric ports before exporting training data.
ipPorts is keyed by p.String(), so a host with both 53/tcp and 53/udp reaches this loop twice. Returning [53,53,...] here skews prediction.Model.Train's host counts and even lets a single unique port bypass the “at least 2 ports” filter.
🩹 Minimal fix
out := make(map[string][]int, len(r.ipPorts))
for ip, ports := range r.ipPorts {
- if len(ports) < 2 {
- continue
- }
- portList := make([]int, 0, len(ports))
- for _, p := range ports {
- portList = append(portList, p.Port)
- }
- out[ip] = portList
+ seen := make(map[int]struct{}, len(ports))
+ for _, p := range ports {
+ seen[p.Port] = struct{}{}
+ }
+ if len(seen) < 2 {
+ continue
+ }
+ portList := make([]int, 0, len(seen))
+ for portNum := range seen {
+ portList = append(portList, portNum)
+ }
+ out[ip] = portList
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for ip, ports := range r.ipPorts { | |
| if len(ports) < 2 { | |
| continue | |
| } | |
| portList := make([]int, 0, len(ports)) | |
| for _, p := range ports { | |
| portList = append(portList, p.Port) | |
| } | |
| out[ip] = portList | |
| for ip, ports := range r.ipPorts { | |
| seen := make(map[int]struct{}, len(ports)) | |
| for _, p := range ports { | |
| seen[p.Port] = struct{}{} | |
| } | |
| if len(seen) < 2 { | |
| continue | |
| } | |
| portList := make([]int, 0, len(seen)) | |
| for portNum := range seen { | |
| portList = append(portList, portNum) | |
| } | |
| out[ip] = portList | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/result/results.go` around lines 233 - 241, The loop over r.ipPorts should
deduplicate numeric ports per host before applying the "at least 2 ports" filter
and before exporting; change the logic in the block that iterates
r.ipPorts/ports (the variable ports and elements p with p.Port and p.String())
to build a set (map[int]struct{}) of unique p.Port values, check if the set size
is < 2 and continue if so, then convert the set keys into the portList slice
(optionally sorted) and assign out[ip] = portList so duplicate numeric ports
(e.g., 53/tcp and 53/udp) are not counted twice.
| // Only add an IPv6 fallback if the IPv4 interface has an IPv6 address, | ||
| // otherwise FindSourceIpForIp will fail with a confusing error. | ||
| if iface := routes[0].NetworkInterface; iface != nil { | ||
| if addrs, err := iface.Addrs(); err == nil { | ||
| for _, a := range addrs { | ||
| if ipNet, ok := a.(*net.IPNet); ok && ipNet.IP.To4() == nil { | ||
| routes = append(routes, &Route{ | ||
| Type: IPv6, | ||
| Default: true, | ||
| DefaultSourceIP: ipNet.IP, | ||
| NetworkInterface: iface, | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify mismatch: fallback accepts any IPv6, while normal source selection rejects link-local IPv6.
rg -n -C3 'To4\(\) == nil|IsLinkLocalUnicast|DefaultSourceIP' pkg/routing/router_bsd.go pkg/routing/router.goRepository: projectdiscovery/naabu
Length of output: 2752
Exclude link-local IPv6 addresses from fallback DefaultSourceIP to align with standard source selection.
The fallback IPv6 source selection (lines 260–266) accepts any IPv6 address, including link-local (fe80::/10). However, the normal source selection in router.go:180 explicitly rejects link-local addresses (!ipAddress.IsLinkLocalUnicast()). When DefaultSourceIP is set, router.go:70 returns it directly without applying the link-local filter, allowing non-routable link-local addresses to be used as source for off-link IPv6 targets.
Suggested patch
- if ipNet, ok := a.(*net.IPNet); ok && ipNet.IP.To4() == nil {
+ if ipNet, ok := a.(*net.IPNet); ok &&
+ ipNet.IP.To4() == nil &&
+ !ipNet.IP.IsLinkLocalUnicast() {
routes = append(routes, &Route{
Type: IPv6,
Default: true,
DefaultSourceIP: ipNet.IP,
NetworkInterface: iface,
})
break
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/routing/router_bsd.go` around lines 255 - 266, The fallback IPv6 route
creation appends any IPv6 addr (from iface.Addrs()) into Route.DefaultSourceIP,
which can include link-local addresses; change the loop in router_bsd.go that
builds the IPv6 fallback so it skips IPs where ipNet.IP.IsLinkLocalUnicast() is
true and only appends a Route{Type: IPv6, Default: true, DefaultSourceIP:
ipNet.IP, NetworkInterface: iface} when the address is not link-local, matching
the source selection logic used elsewhere (see router.go's
!ipAddress.IsLinkLocalUnicast() check) and preventing non-routable link-local
addresses from being used as DefaultSourceIP.
| // Feed on-the-fly service detection | ||
| if r.fpTargetCh != nil { | ||
| hostname := hostResult.IP | ||
| for _, h := range dt { | ||
| if h != "ip" && h != hostResult.IP { | ||
| hostname = h | ||
| break | ||
| } | ||
| } | ||
| for _, p := range hostResult.Ports { | ||
| r.fpTargetCh <- fingerprint.Target{ | ||
| Host: hostname, | ||
| IP: hostResult.IP, | ||
| Port: p.Port, | ||
| TLSDetected: p.TLS, //nolint:staticcheck // deprecated but still set by scan layer | ||
| TLSChecked: true, | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Protect the fingerprint queue from blocking or panicking the scan path.
This send now runs inside OnReceive. Once the 1000-slot buffer fills, result handling blocks behind fingerprinting; later, waitServiceDetection closes the same channel without synchronizing with this send, so a late receive can panic with send on closed channel.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/runner/runner.go` around lines 258 - 276, The send to r.fpTargetCh inside
the OnReceive path can block and also panic if waitServiceDetection closes the
channel concurrently; modify the send so it never blocks and cannot crash: wrap
the send of fingerprint.Target (constructed from hostname, hostResult.IP and
p.Port/TLS fields) in a small helper that does a non-blocking select (case
r.fpTargetCh <- t: default:) and protects against a closed-channel panic with a
defer recover(), so late sends are safely dropped instead of blocking or
panicking.
| ctx, cancel := context.WithCancel(context.Background()) | ||
| r.fpCancel = cancel | ||
| r.fpTargetCh = make(chan fingerprint.Target, 1000) | ||
| r.fpDone = make(chan struct{}) | ||
| r.fpServices = make(map[string]*port.Service) | ||
|
|
||
| resultCh := engine.FingerprintStream(ctx, r.fpTargetCh) |
There was a problem hiding this comment.
Tie service-detection workers to the scan context.
Using context.Background() detaches FingerprintStream from RunEnumeration. Any early return before waitServiceDetection runs—Load, target parsing, or handleNmap failures are enough—leaves the stream workers and collector goroutine parked forever on r.fpTargetCh.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/runner/runner.go` around lines 1134 - 1140, The workers for
FingerprintStream are created with context.WithCancel(context.Background()),
which detaches them from the RunEnumeration lifecycle and can leave goroutines
blocked on r.fpTargetCh; change the created context to derive from the
RunEnumeration context (use the existing ctx passed into RunEnumeration or
r.ctx) by replacing context.WithCancel(context.Background()) with
context.WithCancel(ctx) so that r.fpCancel, r.fpTargetCh, r.fpDone and the
FingerprintStream call (engine.FingerprintStream) are tied to the scan context
and will be cancelled when the parent context is done.
| // Print enriched plain text for -sV mode (before summary) | ||
| if r.options.ServiceVersion && !r.options.JSON && !r.options.CSV && !r.options.DisableStdout { | ||
| for _, p := range hostResult.Ports { | ||
| hostPort := fmt.Sprintf("%s:%d", host, p.Port) | ||
| if _, seen := hostPortPrinted[hostPort]; seen { | ||
| continue | ||
| } | ||
| hostPortPrinted[hostPort] = struct{}{} | ||
| line := hostPort | ||
| if serviceInfo := formatServiceInfo(p.Service); serviceInfo != "" { | ||
| line += " [" + serviceInfo + "]" | ||
| } | ||
| if r.options.OutputCDN && isCDNIP { | ||
| line += " [" + cdnName + "]" | ||
| } | ||
| gologger.Silent().Msgf("%s\n", line) | ||
| } |
There was a problem hiding this comment.
Reuse the enriched text formatter for -o output.
This new -sV branch only enriches stdout. The plain-text file path still goes through WriteHostOutput, so naabu -sV -o out.txt loses the service info that the terminal shows.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/runner/runner.go` around lines 1254 - 1270, The enriched
service/version/CND text is only applied to stdout in the
r.options.ServiceVersion branch, but file output still goes through
WriteHostOutput and loses that info; extract the plain-text enrichment logic
(the code that builds hostPort, appends formatServiceInfo(p.Service) and cdnName
when r.options.OutputCDN && isCDNIP) into a reusable helper (e.g.,
formatEnrichedHostPort or FormatHostOutput) and call it from both the stdout
loop and from WriteHostOutput (or ensure WriteHostOutput invokes that helper
when r.options.ServiceVersion is set) so that -sV combined with -o produces the
same enriched lines in files as on the terminal.
| if retry > 0 { | ||
| hostPorts := r.scanner.ScanResults.GetHostPortsMap() | ||
| if len(hostPorts) > 0 { | ||
| learned := prediction.NewModel() | ||
| learned.Train(hostPorts) | ||
| model.MergeNew(learned) | ||
| gologger.Debug().Msgf("Smart scan: model refined with %d multi-port hosts", len(hostPorts)) |
There was a problem hiding this comment.
Recompute learned probabilities each retry instead of freezing the first observation.
hostPorts is rebuilt from all discoveries on every retry, but MergeNew only inserts pairs that were absent before. The first retry that sees a new pair permanently fixes its probability, so later retries cannot correct early sparse estimates and the queue stays biased.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/runner/smartscan.go` around lines 98 - 104, The current logic trains a
transient model from r.scanner.ScanResults.GetHostPortsMap() and then calls
model.MergeNew(learned), which only adds previously-missing pairs so an early
sparse observation permanently fixes probabilities; instead, recompute and apply
fresh probabilities on every retry by rebuilding the learned model from
hostPorts each time and replacing/updating the main model rather than using
MergeNew. Concretely, in the retry block where you call prediction.NewModel(),
learned.Train(hostPorts) and model.MergeNew(learned), change the merge step to
either replace the model’s probabilities with the newly trained learned model
(e.g., assign or call a full-merge/replace method) or use a merge method that
overwrites existing entries so subsequent retries can correct earlier estimates.
| if sender != nil { | ||
| sender.flush() | ||
| _ = sender.flush() | ||
| } |
There was a problem hiding this comment.
Don't swallow fast-path flush failures.
A sender.flush() error means this batch was not fully emitted. Ignoring it turns transport failures into silent false negatives and makes it impossible to tell whether retries are compensating for a broken sender.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/runner/smartscan.go` around lines 126 - 128, The call to sender.flush()
currently ignores any returned error; instead, check the error returned from
sender.flush() (in the same function where this snippet appears), and do not
swallow it—either return the error up the call stack or wrap it with context and
propagate it (or at minimum log it as an error with sufficient context) so
transport failures are visible; update the code around sender.flush() to handle
the error case rather than assigning it to _.
Native Service Fingerprinting (
-sV)Adds a built-in nmap-compatible service fingerprinting engine that runs directly inside naabu, no external nmap binary required. The engine parses standard
nmap-service-probesfiles (11,951 match rules, 1,200+ identifiable services), sends protocol-specific probes to discovered ports, and matches responses against regex patterns to identify services, versions, and CPEs. Supports TLS-aware probing, probe intensity levels, fast mode, configurable concurrency, and custom probe files.Benchmarked against nmap 7.98 on 8 mock TCP services (SSH, FTP, SMTP, IMAP, POP3, MySQL, HTTP nginx, HTTP Apache):
New flags:
-sD(service discovery),-sV(service version),-sV-fast(port-hinted only),-sV-timeout,-sV-workers,-sV-probes.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation