- 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
cdinto 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 | shor 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:
\uuser,\hhost,\wworking dir,\ttime,\D{...}dateColor 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/awkinside PS1. If you must, sanitize inputs first and prefer doing it in PROMPT_COMMAND.Never use
evalin 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
$PWDor 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.