Skip to content

fix(output): sanitize control bytes and truncate rune-safe before terminal print#336

Open
TBX3D wants to merge 4 commits into
vmfunc:mainfrom
TBX3D:harden-output-clean
Open

fix(output): sanitize control bytes and truncate rune-safe before terminal print#336
TBX3D wants to merge 4 commits into
vmfunc:mainfrom
TBX3D:harden-output-clean

Conversation

@TBX3D

@TBX3D TBX3D commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

several code paths wrote to stderr/stdout directly instead of through the
output sink: module loggers, the whois record dump, and a couple of scan
status lines. under -concurrency>1, only the sink is lock-wrapped, so a
raw fmt.Println/log write could interleave or tear with everything else.
those now route through output.Info/Error.

Sanitize also strips terminal control bytes (OSC, non-SGR CSI, C0/C1,
DEL) from attacker-controlled content before it reaches the terminal,
while keeping \t/\n and SGR color codes intact so styling still works.
progress-item truncation is now rune-aware so a multibyte character
straddling the cut point isn't split into invalid UTF-8.

@TBX3D
TBX3D requested a review from vmfunc as a code owner July 9, 2026 21:17
@github-actions github-actions Bot added the size/l <500 lines changed label Jul 9, 2026
@codecov-commenter

codecov-commenter commented Jul 9, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 66.15385% with 22 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@a38ba0a). Learn more about missing BASE report.

Files with missing lines Patch % Lines
internal/output/output.go 73.68% 14 Missing and 1 partial ⚠️
internal/output/progress.go 0.00% 3 Missing ⚠️
internal/scan/cloudstorage.go 0.00% 1 Missing ⚠️
internal/scan/nuclei.go 0.00% 1 Missing ⚠️
internal/scan/subdomaintakeover.go 0.00% 1 Missing ⚠️
internal/scan/whois.go 0.00% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #336   +/-   ##
=======================================
  Coverage        ?   64.93%           
=======================================
  Files           ?       88           
  Lines           ?     7908           
  Branches        ?        0           
=======================================
  Hits            ?     5135           
  Misses          ?     2379           
  Partials        ?      394           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions Bot added scan changes to scan engine modules changes to scan modules tests test changes labels Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

pr summary

10 files changed (+311 -27)

category files
go source 10
tests 3

@TBX3D
TBX3D force-pushed the harden-output-clean branch 2 times, most recently from 86cb731 to 0440fd1 Compare July 9, 2026 23:52

@vmfunc vmfunc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

the split is right: sanitize at the print boundary, leave extractTitle / ProbeResult.Title / logger.Write byte-accurate so the json + file record stay true and only the terminal gets scrubbed. probe_ansi_test pins exactly that.

sanitize keeping only m-final CSI is correct, sgr color survives, cursor/clear/osc dropped, and it's a linear scan so no redos. output.Info is printf-style so output.Info("%s", result) not output.Info(result) in whois was the right call, keeps a % in a whois record from being eaten.

one nit: Style.Render is a pointer receiver. every call site today is an addressable package var so it's fine, but a non-addressable Style (map elem, or one assigned into an interface) would bind the promoted lipgloss Render and skip sanitize silently. make it a value receiver, nothing here mutates and it closes that off.

conflicting with main, rebase and it's in.

TBX3D added 4 commits July 22, 2026 16:57
response-derived content (page titles, headers, whois records, ...)
reached the sink raw through fmt.Fprintf and lipgloss.Style.Render, so a
hostile response could rewrite the terminal title or clear the screen
via embedded OSC/CSI escape sequences (e.g. probe.go's page-title
highlight). add output.Sanitize, drop C0/C1 control bytes and DEL while
keeping tab/newline and legitimate SGR color sequences, and apply it in
Sink.Info/Success/Warn/Error, ModuleLogger.*, and the Highlight/Muted/
Status/Severity* style renderers so callers get the protection for free.
Whois printed the raw record with the package-level charmbracelet
logger straight to os.Stderr, bypassing apiMode/silent/lockingWriter and
skipping control-byte sanitization; a whois server the target controls
could inject terminal escape sequences. print through output.Info
instead, which is sink-aware and sanitizes the message.
nuclei, cloudstorage, subdomaintakeover, and js/supabase each built their
own charmbracelet logger with a hardcoded os.Stderr writer, bypassing
apiMode/silent/lockingWriter and printing response-derived data outside
the sink the rest of the tool routes through. point them at
output.Writer() instead.
lastItem was sliced by byte index, so a multibyte character straddling
the cutoff produced invalid UTF-8 in the progress line. truncate on
rune boundaries instead.
@TBX3D
TBX3D force-pushed the harden-output-clean branch from a5714c8 to f250590 Compare July 23, 2026 00:10
@TBX3D

TBX3D commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

rebased. output.go was restructured around the Sink type in the meantime, which made this smaller: the package-level Info/Success/Warn/Error all delegate to DefaultSink() now, so Sanitize goes in the four Sink methods and the four ModuleLogger methods instead of the eight separate call sites it used to patch. the Style wrapper over Highlight/Muted/Status/Severity* is unchanged, as are the whois, module-logger and progress-truncation commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

modules changes to scan modules scan changes to scan engine size/l <500 lines changed tests test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants