Skip to content

fix: architectural resolution for #819 using AsyncOutputCoordinator#943

Open
Sebas5207418 wants to merge 6 commits intoprojectdiscovery:mainfrom
Sebas5207418:main
Open

fix: architectural resolution for #819 using AsyncOutputCoordinator#943
Sebas5207418 wants to merge 6 commits intoprojectdiscovery:mainfrom
Sebas5207418:main

Conversation

@Sebas5207418
Copy link

@Sebas5207418 Sebas5207418 commented Mar 5, 2026

This Pull Request addresses the hanging and truncated output issues reported in #819.

I've introduced a dedicated AsyncOutputCoordinator in pkg/output that uses a buffered channel (10k) to decouple scanning from writing. This prevents the 300+ scanning goroutines from blocking the writer, eliminating deadlocks

Key features:

Zero-Blocking Architecture: Decouples scanning from writing via channels.

Data Integrity: Implemented GracefulShutdown to flush the buffer on SIGINT/SIGTERM.

Professional Go Pattern: No heavy mutexes; idiomatic channel communication.

Tested for high-concurrency scans with no data loss.

Summary by CodeRabbit

  • New Features

    • Asynchronous, buffered writing to an output file to reduce blocking and improve throughput.
    • Automatic handling of interrupts to flush pending results and close output files cleanly.
  • Changed Behavior

    • When no input sources are provided, CT log checks are enabled by default and SAN checks are enabled when CT log checks are active.

…a loss

This implementation introduces an asynchronous output coordinator using buffered channels. It resolves issue projectdiscovery#819 by ensuring that concurrent scans do not block the writer and that every JSONL line is properly flushed and closed during shutdown.
This change integrates the AsyncOutputCoordinator in the main process. It ensures that when an output file is specified, all results are handled asynchronously through a buffered channel, preventing deadlocks and truncated JSONL files as reported in issue projectdiscovery#819. It also includes graceful shutdown and signal handling to guarantee data integrity.
@neo-by-projectdiscovery-dev
Copy link

neo-by-projectdiscovery-dev bot commented Mar 5, 2026

Neo - PR Security Review

No security issues found

Highlights

  • AsyncOutputCoordinator now logs write/flush errors instead of silently ignoring them
  • Signal handler no longer calls os.Exit(), allowing deferred cleanup to execute
  • Graceful shutdown mechanism properly integrated with context cancellation
Hardening Notes
  • The code still has a compilation error on line 43 of cmd/tlsx/main.go: it attempts to set options.AsyncOutputCoordinator = coord, but the Options struct in pkg/tlsx/clients/clients.go does not have an AsyncOutputCoordinator field. This will prevent the code from compiling.
  • There are 4 open review comments from previous iterations related to error handling, shutdown race conditions, and input validation. These are code quality/reliability concerns, not exploitable security vulnerabilities.
  • The coordinator implementation uses buffered channels and periodic flushing to prevent blocking - this is a concurrency/performance improvement, not a security fix.
  • File paths come from CLI flags controlled by the user running the tool, so path traversal is not a security concern in this CLI context.

Comment @neo help for available commands. · Open in Neo

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8396dd5-4061-4ed0-a666-26addf409f15

📥 Commits

Reviewing files that changed from the base of the PR and between 68ab6b7 and 2df856c.

📒 Files selected for processing (1)
  • pkg/output/coordinator.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/output/coordinator.go

Walkthrough

Main now creates and wires an exported output.AsyncOutputCoordinator when options.OutputFile is set, calling HandleSignals() and deferring GracefulShutdown() before runner creation; readFlags is invoked with CLI args. Package output gains the coordinator implementation and public API.

Changes

Cohort / File(s) Summary
Main entry point
cmd/tlsx/main.go
Call readFlags(os.Args[1:]); when options.OutputFile is present, construct output.NewAsyncOutputCoordinator(outputFile, 10000, 1*time.Second), assign to options.AsyncOutputCoordinator, call HandleSignals(), and defer GracefulShutdown(); added time and output imports; trimmed flag parsing.
Output coordination
pkg/output/coordinator.go
Add exported AsyncOutputCoordinator and NewAsyncOutputCoordinator(filename string, bufferSize int, flushInterval time.Duration); implement channel-buffered newline-delimited JSON submission, write-loop goroutine with ticker-based flush, Submit(result) error, GracefulShutdown() error, and HandleSignals() for SIGINT/SIGTERM.
API surface
pkg/output
New public API: type AsyncOutputCoordinator and methods NewAsyncOutputCoordinator, Submit, GracefulShutdown, HandleSignals.

Sequence Diagram

sequenceDiagram
    participant Main as Main
    participant Coord as AsyncOutputCoordinator
    participant Writer as WriteLoop
    participant File as FileSystem
    participant Sig as SignalHandler

    rect rgba(200,200,255,0.5)
    Main->>Coord: NewAsyncOutputCoordinator(file, 10000, 1s)
    Coord->>File: open/create file
    Coord->>Writer: start write-loop goroutine
    end

    rect rgba(200,255,200,0.5)
    Main->>Coord: HandleSignals()
    Coord->>Sig: register SIGINT/SIGTERM handler
    end

    rect rgba(255,255,200,0.5)
    Main->>Coord: Submit(result)
    Coord->>Coord: encode JSON + newline
    Coord->>Writer: send bytes on channel
    Writer->>File: write buffered bytes
    Writer->>File: periodic flush (ticker)
    end

    rect rgba(255,200,200,0.5)
    Sig->>Coord: signal received
    Coord->>Writer: trigger graceful shutdown
    Writer->>File: drain, flush, close
    Coord-->>Main: GracefulShutdown completed
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I queue each line with care,
Flushing bright beneath night air,
Signals tap my listening ear,
I drain and close when they appear,
Carrot cheers for every write!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references the architectural solution (AsyncOutputCoordinator) introduced in the changeset to resolve issue #819, accurately summarizing the main change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
cmd/tlsx/main.go (1)

42-42: Don’t discard shutdown errors from deferred cleanup.

Line 42 defers coord.GracefulShutdown() but drops its returned error, so flush/close failures are silent.

Proposed patch
-		defer coord.GracefulShutdown()
+		defer func() {
+			if shutdownErr := coord.GracefulShutdown(); shutdownErr != nil {
+				gologger.Warning().Msgf("output coordinator shutdown failed: %s", shutdownErr)
+			}
+		}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tlsx/main.go` at line 42, The deferred call to coord.GracefulShutdown()
drops its returned error; wrap the defer so the error is checked and handled
(e.g., log or propagate) — replace the bare defer coord.GracefulShutdown() with
a deferred closure that calls coord.GracefulShutdown(), checks the returned
error (if err != nil) and logs it via your logger or returns/sets a named error
return so shutdown failures are not silent; reference the
coord.GracefulShutdown() call and the surrounding main/cleanup logic when making
this change.
pkg/output/coordinator.go (1)

47-49: Per-record flush defeats buffering under high throughput.

Flushing on every write (Line 62) negates most bufio.Writer benefits and increases syscall pressure. With ~300 producers, this can refill the channel and reintroduce backpressure.

Also applies to: 62-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/output/coordinator.go` around lines 47 - 49, The writeLoop in
AsyncOutputCoordinator currently calls Flush() after every write which defeats
bufio buffering; remove the per-write Flush() and instead flush in batches (e.g.
flush when the bufio.Writer.Buffered() >= a configurable threshold or when
Writer.Available() would be exceeded) and also add a periodic flush via a
time.Ticker and a final Flush() when the input channel is closed/worker is
shutting down; update writeLoop and any shutdown/close logic in
AsyncOutputCoordinator to ensure the buffer is flushed on termination and make
the flush threshold configurable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/tlsx/main.go`:
- Around line 35-46: The AsyncOutputCoordinator you create
(AsyncOutputCoordinator) is never used by the runner: NewAsyncOutputCoordinator
is called in main but the coordinator is not passed into runner.New(options) and
output.New(options) still constructs a synchronous fileWriter/StandardWriter, so
Submit is never called and the coordinator is inert; fix by adding a field to
options (e.g., Options.AsyncOutputCoordinator or Options.OutputCoordinator), set
it to the created coord in main before calling runner.New(options), and update
output.New (or runner.New) to detect and use that coordinator when constructing
the writer (or wrap the fileWriter with the coordinator) so all writes go
through coord.Submit; alternatively, if you prefer not to change options, call
coord.Submit for each final write point in main where results are produced to
ensure the coordinator actually receives writes.

In `@pkg/output/coordinator.go`:
- Around line 59-64: The write/flush failures in the coordinator loop are being
silently ignored (calls to c.writer.Write and c.writer.Flush just "continue"),
which can drop JSONL records; update both occurrences (the blocks using
c.writer.Write and c.writer.Flush) to surface errors instead of continuing —
either log the error via the coordinator logger and return it to the caller or
propagate the error up the call stack (e.g., return the error from the enclosing
method), so callers can detect and handle I/O failures; ensure both write and
flush errors are handled consistently and include context (which write/flush on
which writer) when reporting.
- Around line 65-80: The shutdown drain in writeLoop uses a non-blocking select
with default so it can exit while Submit still sends to c.outputChan, causing
accepted but unflushed data; fix by making the drain block and fully consume the
channel until it's closed (remove the default case and use a blocking receive
loop like for { data, ok := <-c.outputChan; if !ok { c.writer.Flush(); return }
... }) and ensure Submit and shutdown coordinate: have the shutdown path close
or mark c.outputChan as closed (or set a closed flag protected by the same
mechanism Submit checks) so Submit stops sending after c.shutdownCtx is
canceled; update the Submit and shutdown logic to use c.shutdownCtx, the closed
flag or channel closure consistently to prevent late sends.
- Around line 110-118: The signal handler in
AsyncOutputCoordinator.HandleSignals currently calls os.Exit(0) which
force-terminates the process and bypasses deferred cleanup; remove the
os.Exit(0) call and ensure the handler only invokes c.GracefulShutdown() (and
optionally notifies the caller via a channel or context cancellation if you need
to propagate shutdown intent). Concretely, edit HandleSignals to keep sigChan
and signal.Notify, call c.GracefulShutdown() inside the goroutine when <-sigChan
receives, but do not call os.Exit; if upstream callers need to observe the
shutdown, add or use an existing shutdown channel or context (e.g., expose
coordinator.ShutdownCh or accept a context) and send a signal on it from the
goroutine instead of exiting.

---

Nitpick comments:
In `@cmd/tlsx/main.go`:
- Line 42: The deferred call to coord.GracefulShutdown() drops its returned
error; wrap the defer so the error is checked and handled (e.g., log or
propagate) — replace the bare defer coord.GracefulShutdown() with a deferred
closure that calls coord.GracefulShutdown(), checks the returned error (if err
!= nil) and logs it via your logger or returns/sets a named error return so
shutdown failures are not silent; reference the coord.GracefulShutdown() call
and the surrounding main/cleanup logic when making this change.

In `@pkg/output/coordinator.go`:
- Around line 47-49: The writeLoop in AsyncOutputCoordinator currently calls
Flush() after every write which defeats bufio buffering; remove the per-write
Flush() and instead flush in batches (e.g. flush when the
bufio.Writer.Buffered() >= a configurable threshold or when Writer.Available()
would be exceeded) and also add a periodic flush via a time.Ticker and a final
Flush() when the input channel is closed/worker is shutting down; update
writeLoop and any shutdown/close logic in AsyncOutputCoordinator to ensure the
buffer is flushed on termination and make the flush threshold configurable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f24f9f07-6a22-441e-9b05-2abb5585684f

📥 Commits

Reviewing files that changed from the base of the PR and between d13b67f and 97c2bf4.

📒 Files selected for processing (2)
  • cmd/tlsx/main.go
  • pkg/output/coordinator.go

Removed per-write flush to improve performance and reduce syscall pressure.

Added periodic flush ticker (1s) for reliable buffered output.

Implemented a robust drain loop in writeLoop to prevent data loss during shutdown.

Improved error handling by using gologger instead of silent continues.

Cleaned up signal handling to avoid os.Exit() and allow deferred cleanups.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
pkg/output/coordinator.go (1)

79-99: ⚠️ Potential issue | 🔴 Critical

Shutdown path can still accept and drop records.

This race remains: after GracefulShutdown() cancels context, Submit() can still choose the send branch, while writeLoop exits shutdown drain on default when the channel is briefly empty. Accepted late submissions can be lost.

Also applies to: 114-118, 124-127

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/output/coordinator.go` around lines 79 - 99, The shutdown race is caused
by Submit still sending to c.outputChan after GracefulShutdown cancels
c.shutdownCtx while writeLoop treats a temporarily-empty channel as terminal;
fix by making shutdown close the channel and preventing further sends: modify
GracefulShutdown to atomically set a shutdown flag and close(c.outputChan) (or
close it once after signalling) so writers in writeLoop can use for range over
c.outputChan to fully drain until closed, and change Submit to check the
shutdown flag or select on c.shutdownCtx.Done() and return an error instead of
sending when shutdown started; update writeLoop to stop using the default
empty-case drain and rely on range/ok semantics to avoid dropping late
submissions (identify Submit, GracefulShutdown, writeLoop, c.outputChan,
c.shutdownCtx, and c.writer).
🤖 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/output/coordinator.go`:
- Around line 132-139: The signal goroutine in
AsyncOutputCoordinator.HandleSignals currently calls c.GracefulShutdown() and
ignores its error; update the goroutine to capture the returned error from
c.GracefulShutdown(), and if non-nil log it (e.g.,
gologger.Error().Err(err).Msg("GracefulShutdown failed during signal handling"))
and optionally exit with a non-zero status. Refer to
AsyncOutputCoordinator.HandleSignals, sigChan/signal.Notify usage and the
c.GracefulShutdown() call so the change only adds error handling and logging for
shutdown failures in the signal handler.
- Line 30: The call to NewAsyncOutputCoordinator is missing the required third
parameter flushInterval; update the invocation where coord, err =
NewAsyncOutputCoordinator(...) (or coord, err =
output.NewAsyncOutputCoordinator(...)) to pass a time.Duration (e.g.,
1*time.Second) as the third argument so it matches the constructor signature
NewAsyncOutputCoordinator(filename string, bufferSize int, flushInterval
time.Duration).
- Around line 30-44: Validate constructor inputs at the top of
NewAsyncOutputCoordinator: check that bufferSize is not negative (return an
error if bufferSize < 0) and that flushInterval is positive (return an error if
flushInterval <= 0) before creating the channel or ticker; update error messages
to clearly reference the offending parameter (bufferSize and flushInterval) so
the caller knows why AsyncOutputCoordinator construction failed (affects
outputChan creation and flushTicker creation).

---

Duplicate comments:
In `@pkg/output/coordinator.go`:
- Around line 79-99: The shutdown race is caused by Submit still sending to
c.outputChan after GracefulShutdown cancels c.shutdownCtx while writeLoop treats
a temporarily-empty channel as terminal; fix by making shutdown close the
channel and preventing further sends: modify GracefulShutdown to atomically set
a shutdown flag and close(c.outputChan) (or close it once after signalling) so
writers in writeLoop can use for range over c.outputChan to fully drain until
closed, and change Submit to check the shutdown flag or select on
c.shutdownCtx.Done() and return an error instead of sending when shutdown
started; update writeLoop to stop using the default empty-case drain and rely on
range/ok semantics to avoid dropping late submissions (identify Submit,
GracefulShutdown, writeLoop, c.outputChan, c.shutdownCtx, and c.writer).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8bdc4e9-a095-4ce5-8ad7-9c5d15fa2198

📥 Commits

Reviewing files that changed from the base of the PR and between 97c2bf4 and 984569f.

📒 Files selected for processing (1)
  • pkg/output/coordinator.go

Removed per-write flush to improve performance. Added periodic flush ticker (1s) for reliable buffered output. Implemented robust drain loop to prevent data loss. Improved error handling with gologger. Fixed signal handling to allow deferred cleanups.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
cmd/tlsx/main.go (1)

35-50: ⚠️ Potential issue | 🟠 Major

Coordinator integration still appears incomplete in the active output path.

At Line 39 you create and attach options.AsyncOutputCoordinator, but runner creation at Line 52 still goes through output.New(options) (per internal/runner/runner.go:137-142). If output.New is not explicitly routing writes to options.AsyncOutputCoordinator.Submit(...), this change remains effectively inert and won’t resolve the hanging/truncation path.

#!/bin/bash
# Verify whether AsyncOutputCoordinator is actually consumed by the output writer path.

# 1) Check options definition includes AsyncOutputCoordinator
rg -nP --type=go '\btype\s+Options\b|AsyncOutputCoordinator' -C3

# 2) Inspect output.New implementation for coordinator usage
rg -nP --type=go '\bfunc\s+New\s*\(' pkg/output/output.go -C40
rg -nP --type=go 'options\.AsyncOutputCoordinator|Submit\s*\(' pkg/output/output.go -C8

# 3) Confirm where Submit is called (should be in active write path)
rg -nP --type=go '\.Submit\s*\(' cmd internal pkg -C3

Expected verification result: pkg/output/output.go (or the writer used by runner.New) should reference options.AsyncOutputCoordinator and dispatch writes via Submit(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tlsx/main.go` around lines 35 - 50, The AsyncOutputCoordinator
initialized and assigned to options.AsyncOutputCoordinator in main (via
output.NewAsyncOutputCoordinator) is never used by the active output path
because runner creation still calls output.New; update the output writer
creation to check options.AsyncOutputCoordinator and route writes through its
Submit(...) API (or wrap the existing writer to call
AsyncOutputCoordinator.Submit) so that when options.AsyncOutputCoordinator !=
nil the output.New/runner.New path delegates actual writes to the coordinator;
locate the affected symbols output.New, runner.New (or
internal/runner/runner.go), options.AsyncOutputCoordinator and the coordinator's
Submit method and modify the writer factory to use the coordinator when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cmd/tlsx/main.go`:
- Around line 35-50: The AsyncOutputCoordinator initialized and assigned to
options.AsyncOutputCoordinator in main (via output.NewAsyncOutputCoordinator) is
never used by the active output path because runner creation still calls
output.New; update the output writer creation to check
options.AsyncOutputCoordinator and route writes through its Submit(...) API (or
wrap the existing writer to call AsyncOutputCoordinator.Submit) so that when
options.AsyncOutputCoordinator != nil the output.New/runner.New path delegates
actual writes to the coordinator; locate the affected symbols output.New,
runner.New (or internal/runner/runner.go), options.AsyncOutputCoordinator and
the coordinator's Submit method and modify the writer factory to use the
coordinator when present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c109313-a14b-4883-ba42-0bb6806d9099

📥 Commits

Reviewing files that changed from the base of the PR and between 984569f and d5d280e.

📒 Files selected for processing (1)
  • cmd/tlsx/main.go

Properly integrated AsyncOutputCoordinator with 1s flush interval. Fixed flag parsing by passing os.Args[1:] to readFlags. Ensured graceful shutdown via defer in the main process flow. Resolved race conditions by aligning main logic with coordinator updates.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/tlsx/main.go`:
- Around line 73-74: The comment placeholders replaced the real flag
registrations, causing flagSet.Parse(args...) to fail; restore the original flag
wiring by re-adding the flag group registrations (e.g., input, scan-mode,
probes, ctlogs, etc.) before the call to flagSet.Parse(args...), ensuring the
same flag variables and registration functions used previously are invoked on
the flagSet variable (look for flagSet and its Parse call in main or setupFlags)
so normal CLI options are accepted again.
- Around line 44-50: The GracefulShutdown() method is not idempotent and can
panic or error if called twice (from coord.HandleSignals() goroutine and the
deferred call); modify the coordinator to guard GracefulShutdown() with a
sync.Once (add a once field, e.g., shutdownOnce sync.Once) and have
GracefulShutdown() invoke the actual shutdown logic inside
shutdownOnce.Do(func(){ ... }) so reads from c.done, closing channels/files, and
other teardown run only once; update any callers (coord.HandleSignals() and the
deferred call in main) to continue calling coord.GracefulShutdown() without
additional guards.
- Line 43: The assignment options.AsyncOutputCoordinator = coord fails because
Options (pkg/tlsx/clients/clients.go) lacks AsyncOutputCoordinator; fix by
either adding a matching field to the Options struct or assigning coord to the
correct existing field: add a field named AsyncOutputCoordinator to Options with
the same type as coord (use the concrete type or interface used where coord is
created), or if there is an intended/typo field (e.g., AsyncCoordinator or
OutputCoordinator), change the assignment to that existing field name instead;
update any constructors/newOptions functions to accept and propagate the new
field if you add it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f3fe109-3b19-49e3-8dcc-c6c246b2f165

📥 Commits

Reviewing files that changed from the base of the PR and between d5d280e and 68ab6b7.

📒 Files selected for processing (1)
  • cmd/tlsx/main.go

if err != nil {
return errkit.Wrapf(err, "could not initialize output coordinator")
}
options.AsyncOutputCoordinator = coord
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Options struct definition and AsyncOutputCoordinator field
rg -nP --type=go 'type\s+Options\s+struct|AsyncOutputCoordinator' -C4 pkg/tlsx/clients/clients.go

Repository: projectdiscovery/tlsx

Length of output: 350


🏁 Script executed:

#!/bin/bash
# Read the full Options struct definition
sed -n '46,120p' pkg/tlsx/clients/clients.go | cat -n

Repository: projectdiscovery/tlsx

Length of output: 3248


🏁 Script executed:

#!/bin/bash
# Continue reading the Options struct
sed -n '76,150p' pkg/tlsx/clients/clients.go | cat -n

Repository: projectdiscovery/tlsx

Length of output: 3120


🏁 Script executed:

#!/bin/bash
# Search for AsyncOutputCoordinator across the entire clients.go file
rg -n "AsyncOutputCoordinator" pkg/tlsx/clients/clients.go

Repository: projectdiscovery/tlsx

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search entire repository for AsyncOutputCoordinator
rg -n "AsyncOutputCoordinator" --type=go

Repository: projectdiscovery/tlsx

Length of output: 1116


🏁 Script executed:

#!/bin/bash
# Find where Options struct ends
rg -n "^}" pkg/tlsx/clients/clients.go -B5 | head -20

Repository: projectdiscovery/tlsx

Length of output: 805


🏁 Script executed:

#!/bin/bash
# Check the Options struct from line 46 to 188
sed -n '46,188p' pkg/tlsx/clients/clients.go | tail -20

Repository: projectdiscovery/tlsx

Length of output: 801


🏁 Script executed:

#!/bin/bash
# Check the context around line 43 in main.go
sed -n '35,50p' cmd/tlsx/main.go

Repository: projectdiscovery/tlsx

Length of output: 615


Compile error: AsyncOutputCoordinator field does not exist in Options struct.

Line 43 assigns options.AsyncOutputCoordinator = coord, but the Options struct in pkg/tlsx/clients/clients.go (lines 46–188) does not define this field. This will cause a compile-time error: "unknown field AsyncOutputCoordinator in struct literal".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tlsx/main.go` at line 43, The assignment options.AsyncOutputCoordinator =
coord fails because Options (pkg/tlsx/clients/clients.go) lacks
AsyncOutputCoordinator; fix by either adding a matching field to the Options
struct or assigning coord to the correct existing field: add a field named
AsyncOutputCoordinator to Options with the same type as coord (use the concrete
type or interface used where coord is created), or if there is an intended/typo
field (e.g., AsyncCoordinator or OutputCoordinator), change the assignment to
that existing field name instead; update any constructors/newOptions functions
to accept and propagate the new field if you add it.

Comment on lines +44 to +50
coord.HandleSignals()

defer func() {
if err := coord.GracefulShutdown(); err != nil {
gologger.Warning().Msgf("Error during graceful shutdown: %v", err)
}
}()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "coordinator.go" -type f

Repository: projectdiscovery/tlsx

Length of output: 92


🏁 Script executed:

wc -l ./pkg/output/coordinator.go

Repository: projectdiscovery/tlsx

Length of output: 96


🏁 Script executed:

cat -n ./pkg/output/coordinator.go

Repository: projectdiscovery/tlsx

Length of output: 4786


Ensure GracefulShutdown() is idempotent before calling it from two paths.

coord.HandleSignals() triggers shutdown via goroutine on SIGINT/SIGTERM (line 138 in coordinator.go), and Lines 46-50 defer another shutdown call. The GracefulShutdown() implementation (lines 124-128) is not idempotent:

  • Reading from already-closed c.done channel on second call will panic
  • Closing an already-closed file on second call will error
  • No sync.Once or equivalent guard protects against repeated invocation

Protect GracefulShutdown() with sync.Once to make it safe for concurrent/repeated calls from both signal handler and deferred shutdown.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tlsx/main.go` around lines 44 - 50, The GracefulShutdown() method is not
idempotent and can panic or error if called twice (from coord.HandleSignals()
goroutine and the deferred call); modify the coordinator to guard
GracefulShutdown() with a sync.Once (add a once field, e.g., shutdownOnce
sync.Once) and have GracefulShutdown() invoke the actual shutdown logic inside
shutdownOnce.Do(func(){ ... }) so reads from c.done, closing channels/files, and
other teardown run only once; update any callers (coord.HandleSignals() and the
deferred call in main) to continue calling coord.GracefulShutdown() without
additional guards.

Comment on lines +73 to +74
// [Tutte le flag groups rimangono identiche al codice originale...]
// [Inclusi tutti i gruppi: input, scan-mode, probes, ctlogs, ecc...]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical regression risk: flag registrations appear replaced by placeholders.

Lines 73-74 are comments, not actual flag group wiring. If the flag definitions were removed here, flagSet.Parse(args...) will reject normal CLI options and break core behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tlsx/main.go` around lines 73 - 74, The comment placeholders replaced the
real flag registrations, causing flagSet.Parse(args...) to fail; restore the
original flag wiring by re-adding the flag group registrations (e.g., input,
scan-mode, probes, ctlogs, etc.) before the call to flagSet.Parse(args...),
ensuring the same flag variables and registration functions used previously are
invoked on the flagSet variable (look for flagSet and its Parse call in main or
setupFlags) so normal CLI options are accepted again.

Added input validation in NewAsyncOutputCoordinator to prevent panics with invalid bufferSize or flushInterval. Refactored writeLoop to use 'for range' on outputChan for guaranteed data drainage during shutdown. Implemented consistent error logging via gologger to prevent silent I/O failures. Optimized GracefulShutdown sequence to ensure all pending results are flushed before file closure. Added proper error handling in HandleSignals goroutine.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant