Skip to content

fix(barbuk): strip control characters from prompt segment output - #2397

Merged
seefood merged 4 commits into
Bash-it:masterfrom
carfeii:fix/barbuk-strip-control-chars
Aug 9, 2026
Merged

fix(barbuk): strip control characters from prompt segment output#2397
seefood merged 4 commits into
Bash-it:masterfrom
carfeii:fix/barbuk-strip-control-chars

Conversation

@carfeii

@carfeii carfeii commented Aug 7, 2026

Copy link
Copy Markdown

Fixes #2396

The python_venv segment falls back to reading pyproject.toml's requires-python field (via awk, no character restrictions on the value) when no active virtualenv is detected, and every segment's output was concatenated into PS1 unfiltered in __prompt-command. A crafted pyproject.toml could inject terminal escape sequences into the prompt simply by cd-ing into the directory, since python_venv is part of the default barbuk segment list.

Change

Strips control characters from each segment's output in __prompt-command, the single point every segment's output already passes through before being appended to PS1, rather than only in python_venv, so any future segment that reads file content is covered too.

Testing

  • Reproduced the issue with a pyproject.toml whose requires-python field contained a raw OSC title-set escape sequence, confirmed via a tmux pipe-pane capture (hex-dumped, not just visually inspected) that the raw escape bytes reached the terminal adjacent to the theme's own SGR color codes.
  • Re-ran the same reproduction against this fix; the injected control bytes are gone, only harmless printable text remains, and unrelated legitimate color codes nearby are untouched.
  • Note: a first attempt at the strip pattern (starting the character range at $'\x00') silently matched nothing, since bash strings can't hold a literal NUL byte and it collapses out of the range at parse time. Starting at $'\x01' fixed it; caught this by testing the substitution in isolation rather than trusting it from source alone.

The python_venv segment falls back to reading pyproject.toml's
requires-python field with no character restrictions, and every
segment's output was concatenated into PS1 unfiltered. A crafted
pyproject.toml could inject terminal escape sequences into the
prompt simply by cd-ing into the directory.

Strips control characters from each segment's output in
__prompt-command, the single point all segments already pass
through, before it's appended to PS1.
@BarbUk

BarbUk commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

This is a good find, thanks.

This will remove all control characters, even color.
This break the git prompt for example.

This also does not protect against non raw control character, e.g,

requires-python = "\e]0;change title\a"

Maybe we can sanitize with awk directly ?

$ grep requires-python pyproject.toml

requires-python = ">=3.12" # Normal
requires-python = "\e]0;change title\a" # non raw control char
requires-python = "^K^?^B" # raw control char (ctrl+v backspace, ctrl+v escape)
$ awk -F'"' '/^requires-python/ {gsub(/([^\11\12\15\40-\176]|\\[eE]\])/, ""); print $0}' pyproject.toml

requires-python = "0;change title\a"
requires-python = ">=3.12"
requires-python = ""

Gogs added 2 commits August 8, 2026 15:34
…segment

The previous fix stripped control characters from every segment's
rendered output in __prompt-command, which also stripped legitimate
ANSI color codes (e.g. the git segment's branch/status coloring),
breaking prompt coloring for every user, not just the vulnerable one.

It also only handled raw control bytes, not the same escape sequence
written as literal backslash-letter text (e.g. "\e]0;...\a"), which
some shells/echo modes can still expand.

Sanitize at the actual source instead: strip anything outside
printable ASCII, plus literal backslash-letter escape sequences,
directly in the awk extraction of pyproject.toml's requires-python
field, the one value that's genuinely untrusted. Every other
segment's output, including its own legitimate color codes, is left
untouched.

Thanks @BarbUk for catching both issues.
@carfeii

carfeii commented Aug 8, 2026

Copy link
Copy Markdown
Author

Thanks for the careful review, both points are right.

Pushed a fix that reverts the blanket strip in __prompt-command (which was indeed stripping the ESC byte from every segment's own legitimate SGR color codes, not just the untrusted value) and instead sanitizes requires-python directly at extraction in __python_venv_prompt, along the lines you suggested:

python_info=$(awk -F'"' '/^requires-python/ {gsub(/[^\40-\176]|\\[a-zA-Z]/, "", $2); print $2}' pyproject.toml)

This strips both raw control bytes and the same sequence written as literal backslash-letter text (e.g. "\e]0;...\a"), so it also covers the non-raw case you pointed out.

Re-verified locally: git branch coloring is intact again (\e[0;36mmaster, \e[32;1m+1, \e[31;1m✗ all present unmodified in a raw pipe-pane capture), and the original PoC's injected ESC/BEL bytes are gone from the python_venv segment either way (raw or literal-text form).

@BarbUk

BarbUk commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Just checked why the git prompt was impact by the raw char cleaning and not the other segment.

The git_prompt_info function return the prompt with echo -e.

Returning the git prompt as a string, we can sanitize using your first proposal.
This has the added benefice or sanitizing all segments.

Sanitizing should use the control code class, it's simplier:
[[:cntrl:]]

Updated code should look like:

diff --git a/themes/barbuk/barbuk.theme.bash b/themes/barbuk/barbuk.theme.bash
index e3f658f7..c79dbf36 100644
--- a/themes/barbuk/barbuk.theme.bash
+++ b/themes/barbuk/barbuk.theme.bash
@@ -106,7 +106,7 @@ function __git-upstream-remote-logo_prompt() {
 
 function git_prompt_info() {
 	git_prompt_vars
-	echo -e "on $SCM_GIT_CHAR_ICON_BRANCH $SCM_PREFIX$SCM_BRANCH$SCM_STATE$SCM_GIT_AHEAD$SCM_GIT_BEHIND$SCM_GIT_STASH$SCM_SUFFIX "
+	echo "on $SCM_GIT_CHAR_ICON_BRANCH $SCM_PREFIX$SCM_BRANCH$SCM_STATE$SCM_GIT_AHEAD$SCM_GIT_BEHIND$SCM_GIT_STASH$SCM_SUFFIX "
 }
 
 function __exit_prompt() {
@@ -317,6 +317,7 @@ function __prompt-command() {
 	for segment in $BARBUK_PROMPT; do
 		local info
 		info="$(__"${segment}"_prompt)"
+		info="${info//[[:cntrl:]]/}"
 		[[ -n "${info}" ]] && PS1+="${info}"
 	done

… echo -e

git_prompt_info used 'echo -e' to expand its own color codes, which
were stored as literal backslash-escape text (e.g. lib/colors.bash's
bold_red) rather than real bytes. Applying the blanket control-
character strip in __prompt-command without first removing that -e
flag broke coloring, since by the time the strip ran, echo -e had
already turned the git segment's own colors into real bytes
indistinguishable from a malicious payload.

Dropping -e means git_prompt_info's own escape codes are no longer
expanded from literal text at echo time (removing the injection
surface that flag represented for any future untrusted input reaching
this function), and re-adds the blanket [[:cntrl:]] strip at
__prompt-command, this time safely, since no segment's construction
path relies on echo -e turning literal text into real bytes anymore.

Verified live: git branch coloring intact in a raw pipe-pane capture
(same bytes as before this change), and the original PoC's injected
control bytes still stripped from the python_venv segment.
@carfeii

carfeii commented Aug 8, 2026

Copy link
Copy Markdown
Author

Good catch. git_prompt_info() was the only segment using echo -e, so it was the only one turning its own literal color text (\[\e[31;1m\] etc. from lib/colors.bash) into real escape bytes at print time, which is what the blanket strip was destroying along with the payload.

Applied both changes you suggested: dropped -e from git_prompt_info(), and put info="${info//[[:cntrl:]]/}" back in __prompt-command. Retested with the same raw pty capture method as before, colored git branch/status segments come through unchanged and the injected control bytes are still stripped. Pushed as a new commit on this branch.

@seefood

seefood commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

@BarbUk are you ready to give it an official green review for me to merge?

@BarbUk

BarbUk commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

LGTM

@seefood seefood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

approving for @BarbUk

@seefood
seefood merged commit fab8f57 into Bash-it:master Aug 9, 2026
6 checks passed
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.

barbuk theme's Python venv segment isn't stripped of terminal escape sequences

3 participants