Conversation
NullWriter.isatty() returned True. NullWriter discards everything written to it, so it has no terminal to color in the first place -- it should always report False. That mattered because NULL_PRINTER is built at import time as Printer(out_stream=NullWriter(), quiet=True). Printer.__init__ defaults ansi_color to out_stream.isatty() and calls colorama.init() as a side effect whenever ansi_color ends up True. With the old isatty() returning True, importing graphtage.printer unconditionally triggered colorama.init() at import time, before __main__.py ever parsed --color. colorama.init() replaces sys.stdout/sys.stderr with its own stripping wrapper. Any later, real Printer built by main() on top of that already-wrapped stdout inherits a stream that strips ANSI escapes whenever the destination isn't a real terminal -- exactly the redirected/piped case the issue reports, and exactly the case where someone would pass --color to force it. The flag was silently swallowed by import-order side effects it had no way to know about. Fix: NullWriter.isatty() now returns False. NULL_PRINTER.ansi_color is then False, colorama.init() is no longer called at import time, and sys.stdout is left alone for main() to wrap correctly when --color is passed. Testing: - New test/test_printer.py (4 tests): NullWriter.isatty() is False and NULL_PRINTER.ansi_color is False; importing graphtage.printer in a fresh subprocess leaves sys.stdout unmodified; an end-to-end repro of the issue (graphtage --no-status --color a.json b.json with stdout captured/redirected) now emits ANSI escapes, where it emitted none before the fix. - Confirmed the new tests fail with the expected AssertionErrors against the unmodified code (git stash) and pass against the fix. - Ran the full existing test suite (test/), all passing, no regressions. Fixes trailofbits#128. This PR was prepared with AI assistance (Claude) under my direction: I reviewed the root-cause analysis and the diff before opening it. Signed-off-by: agu2347 <agu2347@users.noreply.github.com>
|
|
|
Heads up: I'm not able to sign the CLA on behalf of the account here (that's a legal agreement tied to the individual, not something an automated contribution workflow should click through). If a maintainer is fine merging without it, or if the account holder wants to sign it themselves via the linked page, this can move forward -- otherwise no worries, feel free to close it. |
|
Thank you for the careful write-up — the root-cause analysis here is good, and the mechanism you describe was real. It was also already fixed. $ graphtage --no-status --color a.json b.json > out.txt
$ grep -c $'\x1b' out.txt
8
The branch here is based on a much older So I'm closing this one — not because the analysis was wrong, but because the bug it targets is already gone and the |
Root cause
NullWriter.isatty()returnedTrue.NullWriterdiscards everything written to it, so it has no terminal to color in the first place -- it should always reportFalse.That mattered because
NULL_PRINTERis built at import time:Printer.__init__defaultsansi_colortoout_stream.isatty()whenansi_coloris omitted, and callscolorama.init()as a side effect wheneveransi_colorends upTrue. With the oldisatty()returningTrue, simplyimport graphtage.printerunconditionally triggeredcolorama.init()before__main__.pyever got a chance to parse--color.colorama.init()replacessys.stdout/sys.stderrwith its own stripping wrapper. Any later, realPrinterbuilt bymain()on top of that already-wrappedstdoutinherits a stream that strips ANSI escapes whenever the destination isn't a real terminal -- exactly the redirected/piped case this issue reports, and exactly the case where a user would pass--colorto force it. The flag was silently swallowed by an import-order side effect it had no way to know about.Fix
NullWriter.isatty()now returnsFalse.NULL_PRINTER.ansi_coloris thenFalse,colorama.init()is no longer called at import time, andsys.stdoutis left alone formain()to wrap correctly once--coloris actually parsed.Testing
Added
test/test_printer.py(4 tests):NullWriter().isatty()isFalse, andNULL_PRINTER.ansi_colorisFalse.graphtage.printerin a fresh subprocess leavessys.stdoutunmodified (before is after).python -m graphtage --no-status --color a.json b.jsonwith stdout captured now emits ANSI escapes (\x1b), where it emitted none before the fix.Verified the new tests fail with the expected
AssertionErrors against the unmodified code (git stash) and pass against the fix, so they're not tautological. Also ran the full existingtest/suite -- all passing, no regressions.Fixes #128.
Disclosure: this PR was prepared with AI assistance (Claude), used under my direction as the account owner. I reviewed the root-cause analysis, diff, and tests before opening it.