Install
$ agentstack add skill-jayrha-agentskills-bash-script-hardening ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Bash Script Hardening
Overview
Most Bash bugs are silent: an unset variable expands to nothing, a failed command is ignored, an unquoted path word-splits, and the script marches on corrupting state. This skill makes Bash scripts fail loudly, fail early, and clean up after themselves.
Keywords: bash, shell, strict mode, set -euo pipefail, IFS, quoting, word splitting, globbing, ShellCheck, trap, ERR, EXIT, cleanup, mktemp, signal handling, defensive scripting, POSIX, exit codes, here-doc, arrays.
Use this skill to author a new script from a hardened template, retrofit an existing script, or review a script for safety defects.
Workflow
- Set the shebang and strict mode. Use
#!/usr/bin/env bashand the canonical strict-mode preamble (see below). Never rely on/bin/shif Bash features are used. - Lock down word splitting. Set
IFS=$'\n\t'so unquoted expansions don't split on spaces. - Quote everything. Every variable expansion and command substitution gets double quotes unless you have a deliberate, commented reason not to.
- Use arrays for lists and command arguments. Never build commands by concatenating strings.
- Add a cleanup trap. Register an
EXITtrap that removes temp files and restores state, plus anERRtrap for diagnostics. - Handle errors explicitly where strict mode is insufficient (conditionals, command substitution in older Bash, pipelines you intend to tolerate).
- Create temp files safely with
mktemp, and reference them through the cleanup trap. - Validate inputs and dependencies up front: required args, required commands (
command -v), required env vars. - Run ShellCheck and resolve every finding; suppress only with a justified
# shellcheck disable=SCxxxxcomment. - Test failure paths, not just the happy path.
Reference files:
references/strict-mode.md— deep dive onset -euo pipefail, IFS, and the gotchas/escape hatches.references/shellcheck-codes.md— the most common ShellCheck codes, what they mean, and the correct fix.templates/hardened-script.sh— drop-in starting template with strict mode, traps, arg parsing, logging.scripts/harden_check.sh— static auditor that flags missing strict mode, unquoted vars, and other red flags.examples/before-after.md— a real fragile script transformed into a hardened one, line by line.
The canonical strict-mode preamble
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
What each flag buys you:
| Flag | Effect | Why it matters | |------|--------|----------------| | -e (errexit) | Exit on any command returning non-zero | Stops a broken script from continuing on corrupt state | | -u (nounset) | Error on use of unset variables | Catches typos like "$fle" and missing args | | -o pipefail | A pipeline fails if any stage fails | cmd \| grep x no longer hides cmd crashing | | -E (errtrace) | ERR trap is inherited by functions/subshells | Your error trap actually fires inside functions | | IFS=$'\n\t' | Split only on newline/tab, not spaces | Filenames with spaces stop silently breaking loops |
> -e has sharp edges. Read references/strict-mode.md for where it does NOT trigger (e.g. inside if, &&, ||, the last command of a function used as a condition) and the correct patterns to keep error checking explicit there.
Quoting and word-splitting rules
- Always quote expansions:
"$var","${arr[@]}","$(cmd)". - Use
"${arr[@]}"(quoted,@) to preserve elements; never${arr[*]}for argument lists. - Prefer
[[ ... ]]over[ ... ]in Bash — it does not word-split or glob inside. - Use
printf '%s\n' "$x"instead ofecho "$x"for arbitrary data (echo mangles-n, backslashes). - Loop over files with globs or
find -print0 | while IFS= read -r -d '', neverfor f in $(ls).
# WRONG — word-splits and globs
for f in $(ls *.txt); do rm $f; done
# RIGHT
shopt -s nullglob
for f in ./*.txt; do
rm -- "$f"
done
Traps and cleanup
Register cleanup once, early, and make it idempotent:
cleanup() {
local rc=$?
# remove temp artifacts; restore anything you changed
[[ -n "${tmpdir:-}" && -d "$tmpdir" ]] && rm -rf -- "$tmpdir"
return $rc
}
trap cleanup EXIT
on_err() {
local rc=$? line=$1
printf 'ERROR: line %s exited with status %s\n' "$line" "$rc" >&2
}
trap 'on_err "$LINENO"' ERR
The EXIT trap runs on normal exit, error exit (with -e), and most signals — making it the single reliable place to clean up. See templates/hardened-script.sh for the full pattern including signal-specific traps.
Safe temp files
tmpdir="$(mktemp -d)" # never hand-craft /tmp/$$ paths (race + predictable)
tmpfile="$(mktemp "$tmpdir/XXXXXX.json")"
# tmpdir is removed by the EXIT trap above
Never use /tmp/myscript.$$ — predictable names are a symlink-attack vector and collide.
Input and dependency validation
require_cmd() {
command -v "$1" >/dev/null 2>&1 || { printf 'Missing dependency: %s\n' "$1" >&2; exit 127; }
}
require_cmd jq
require_cmd curl
: "${API_TOKEN:?API_TOKEN must be set}" # nounset-style guard for required env
if (( $# \n' "${0##*/}" >&2
exit 2
fi
Hardening checklist
- [ ]
#!/usr/bin/env bashshebang - [ ]
set -Eeuo pipefailandIFS=$'\n\t' - [ ] Every variable/command-substitution expansion is double-quoted
- [ ] Lists and argv built as arrays, expanded with
"${arr[@]}" - [ ]
EXITtrap cleans temp files;ERRtrap reports failures - [ ] Temp files via
mktemp/mktemp -d, removed in trap - [ ] Required commands checked with
command -v - [ ] Required args and env vars validated with clear usage + nonzero exit
- [ ]
--used before user-supplied paths inrm,cp, etc. - [ ] No
eval; no parsingls; no unquoted$(...) - [ ] Meaningful, distinct exit codes (2 usage, 127 missing dep, etc.)
- [ ]
shellcheckpasses clean (disables are justified inline)
Best Practices
- Make scripts re-runnable (idempotent). Guard mutations so a re-run after partial failure is safe.
- Separate config from logic. Read tunables from env with documented defaults:
: "${TIMEOUT:=30}". - Log to stderr, data to stdout. Keep stdout pure so the script composes in pipelines.
- Use functions with
localvariables; avoid global mutation. Declarelocalseparately from command substitution to avoid masking exit codes (local x; x="$(cmd)"). - Pin behavior, not the environment. Use full option names in comments; prefer
printfoverecho. - Fail with the right exit code. Reserve nonzero codes for distinct failure classes so callers can branch.
- Run ShellCheck in CI and treat it as a gate. See
references/shellcheck-codes.md.
Common Pitfalls
local x=$(cmd)swallows errors. Thelocalbuiltin's own success maskscmd's exit status even under-e. Split into two lines.-edoes not fire insideif/&&/||/!or the condition of a loop. Don't assume strict mode covers everything; check return codes explicitly there.cmd | while read ...runs the loop in a subshell — variables set inside are lost in older Bash. Use process substitutionwhile read ...; do ...; done < <(cmd)instead.set -e+ a function whose last command "fails" as a test silently disables errexit for that function's body. Avoid using such functions asifconditions when you also rely on-einside them.- Unquoted
$@vs"$@". Bare$@re-splits arguments; always"$@". readwithout-rmangles backslashes; alwaysread -r, and setIFS=per-read to preserve leading/trailing whitespace.- Predictable temp paths (
/tmp/$$) cause races and symlink attacks — alwaysmktemp. rm -rf "$dir/"*when$diris empty/unset can target/. Quote, validate, and use--; consider${dir:?}to abort on empty.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: JayRHa
- Source: JayRHa/AgentSkills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.