Posted on
Artificial Intelligence

Prompt Security

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Prompt Security: Hardening Your Bash Prompt Before It Bites Back

Every time your cursor blinks, your shell runs code. That code—your prompt—often executes on every command. One slow or unsafe snippet in your PS1 or PROMPT_COMMAND can leak secrets, get hijacked by a crafted directory or Git branch, or just grind your terminal to a crawl. The good news: a few practical safeguards go a long way.

This guide explains why prompt security matters, how attacks happen, and gives you actionable steps (with copy-pasteable Bash) to harden your setup—without giving up useful features like Git status or directory info.

Why “prompt security” is a real issue

  • The prompt executes code constantly. Command substitutions like $(...) in PS1 and hooks in PROMPT_COMMAND run every time the prompt renders—meaning any bug or hijack is repeatedly triggered.

  • Your prompt displays untrusted data. Current working directory names and Git branch names are attacker-controlled if you cd into someone else’s project. Those names can contain control characters (ANSI/OSC escapes) that:

    • Fake typed output or “phish” you by repainting the screen
    • Interfere with copy/paste (OSC 52 clipboard ops)
    • Break line editing/history
  • PATH hijacking risk. If your prompt calls external tools (e.g., git, sed, awk) via bare names, a writable directory earlier in PATH can replace them and gain code execution each time your prompt draws.

  • Plugin frameworks and “one-liner installers.” Piping curl | sh or using unvetted prompt plugins introduces supply-chain risk in the highest-frequency code path you have.

TL;DR actionable checklist

  • Keep prompts simple: prefer Bash built-ins (\u \h \w \t) over $(...).

  • Sanitize any untrusted text (PWD, VCS names) to remove control chars.

  • Pin absolute paths for any external command used by your prompt.

  • Use PROMPT_COMMAND safely (avoid eval; prefer array form).

  • Treat root’s prompt as ultra-minimal.

Details and ready-to-use examples below.


1) Start safe: prefer built-ins over external commands

Bash provides a lot of prompt information without running extra programs:

  • \u user, \h host, \w working dir, \t time, \D{...} date

  • Color sequences must be wrapped with \[ and \] so readline counts properly.

A simple, fast, and safe baseline:

PS1='\u@\h:\w\$ '

This avoids $(...) entirely. If that’s “enough,” stop here—you’ve reduced both latency and attack surface.

2) If you must run commands, pin paths and sanitize

When you really want VCS info, compute it safely and treat outputs as untrusted. The two big rules:

  • Call external commands via absolute paths (avoid PATH hijack).

  • Strip control characters from anything that comes from the filesystem or repo metadata.

Drop these helpers into your ~/.bashrc:

# Remove control chars (C0 + DEL) but keep UTF-8; cap length to 128
sanitize() {
  local s
  s=$(printf '%s' "$1" | LC_ALL=C tr -d '\000-\037\177')
  printf '%s' "${s:0:128}"
}

# Precompute a safe $PWD for the prompt
__SAFE_PWD=''
__update_safe_pwd() {
  __SAFE_PWD=$(sanitize "$PWD")
}

# Optional: show current Git branch safely (requires /usr/bin/git)
__GIT_SEG=''
__update_git_seg() {
  local G=/usr/bin/git b
  PATH=/usr/bin:/bin  # constrain PATH for this function
  [ -x "$G" ] || { __GIT_SEG=''; return; }
  b=$("$G" rev-parse --abbrev-ref HEAD 2>/dev/null) || { __GIT_SEG=''; return; }
  b=$(sanitize "$b")
  __GIT_SEG=${b:+ ($b)}
}

# Use PROMPT_COMMAND to update variables, then render minimal PS1
# Prefer array form (Bash 5+) when available
if declare -p PROMPT_COMMAND 2>/dev/null | grep -q 'declare \-a'; then
  PROMPT_COMMAND=(__update_safe_pwd __update_git_seg "${PROMPT_COMMAND[@]}")
else
  PROMPT_COMMAND="__update_safe_pwd; __update_git_seg; $PROMPT_COMMAND"
fi

PS1='\u@\h:${__SAFE_PWD}${__GIT_SEG}\$ '

Why this is safer:

  • No $(...) inside PS1; all computation happens in controlled functions.

  • External command paths are pinned (/usr/bin/git), and PATH is narrowed inside the function.

  • Both PWD and branch name are sanitized to remove control characters.

Real-world impact:

  • A malicious branch named with OSC 52 could attempt to mess with your clipboard each time the prompt renders. Sanitization strips the escape sequence so it prints as harmless text.

Example of a malicious branch (don’t do this on your real repos):

/usr/bin/git checkout -b $'\e]52;c;U2FmZS1Qcm9tcHQ=\a'

Without sanitization, many terminals will interpret this; with the helper functions above, the escape is removed.

3) Avoid dangerous patterns in PS1 and PROMPT_COMMAND

  • Don’t parse command output that includes filenames or branch names with naive sed/awk inside PS1. If you must, sanitize inputs first and prefer doing it in PROMPT_COMMAND.

  • Never use eval in PROMPT_COMMAND.

  • Prefer the array form of PROMPT_COMMAND (Bash 5+) so each hook is a distinct command rather than a single “concatenated” string vulnerable to injection via a variable.

Check your current setup:

declare -p PS1 PROMPT_COMMAND 2>/dev/null

If you see things like backticks/$(...) parsing git status directly inside PS1, refactor into safe helper functions or drop the feature.

4) Keep root’s prompt ultra-minimal

If your prompt code runs as root, any mistake has maximal blast radius. Use a bare-bones root prompt and avoid external commands entirely:

# In /root/.bashrc or gated by [ "$EUID" -eq 0 ]
PS1='\u@\h:\w# '
unset PROMPT_COMMAND

5) Prefer packaged prompt frameworks over “curl | sh” (optional)

If you want a feature-rich, maintained prompt, install via your distro’s package manager rather than piping scripts from the internet. Starship is a good example (cross-shell, written in Rust) and generally careful about escaping.

Install Starship via your package manager:

  • Debian/Ubuntu (apt):
sudo apt update && sudo apt install starship
  • Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install starship
  • openSUSE (zypper):
sudo zypper install starship

Then enable it for Bash:

echo 'eval "$(starship init bash)"' >> ~/.bashrc

Even with frameworks, keep the earlier principles in mind: avoid running external commands for root; be wary of user-controlled segments; prefer distro packages over ad-hoc installers.

Bonus: install or verify Git via your package manager

If your prompt uses Git info, make sure Git is installed from your distro:

  • Debian/Ubuntu (apt):
sudo apt update && sudo apt install git
  • Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install git
  • openSUSE (zypper):
sudo zypper install git

Quick audit: things to look for right now

  • Does PS1 contain $(...) or backticks that call external tools? Move that logic into PROMPT_COMMAND functions, pin paths, and sanitize.

  • Does your prompt display raw $PWD or Git branch names? Sanitize before display.

  • Is PROMPT_COMMAND a big concatenated string with semicolons? Consider array form and remove any eval.

  • Are you running the same complex prompt as root? Simplify it drastically.

Conclusion and next steps (CTA)

Your prompt runs more than any other code you write. Treat it like production: keep it simple, sanitize untrusted inputs, and lock down external calls.

Next steps: 1) Paste the safe helpers from section 2 into your ~/.bashrc and restart your shell. 2) Audit PS1 and PROMPT_COMMAND for risky patterns and refactor. 3) If you want advanced features, install a maintained framework via your package manager (e.g., Starship), not curl | sh.

If you found this useful, share your hardened PS1 in a gist and link to this article—let’s make “prompt security” the default for everyone who lives in a terminal.