/shell-hardening
Bash security checklist: injection, set -euo, printf, quoting. Triggers: shell hardening, bash security.
$ golems-cli skills install shell-hardeningUpdated 2 weeks ago
Apply to EVERY bash script before committing. The March 26 overnight sprint found security issues in every orchestrator worker PR (D-R1 through D-R6). Common patterns: JSON injection via printf, shell metacharacter bypass, unquoted paths, unquoted heredoc delimiters.
Checklist
1. String Interpolation (CRITICAL)
- NEVER use
printfwith format strings to construct JSON — usejq - NEVER use
echo "$var"in command construction — use arrays - NEVER use
evalorbash -c "$string"with user input
# WRONG — JSON injection
printf '{"name": "%s"}' "$user_input"
# RIGHT — jq handles escaping
jq -n --arg name "$user_input" '{"name": $name}'1b. Heredoc Delimiters (CRITICAL when writing markdown)
- ALWAYS quote the heredoc delimiter (
<<'EOF') when writing or appending ANY content containing backticks or$()— markdown code spans count - Unquoted
<<EOFcommand-substitutes the body: every backtick span and$()EXECUTES
# WRONG — unquoted delimiter: the shell evaluates the backtick code span.
# Canonical incident (2026-06-07, PID 36248): writing exactly this kind of
# markdown note launched `voicelayer serve` against the default socket — the
# kickoff's ONE forbidden thing — while the live VoiceBar was in use.
cat >> notes.md <<EOF
Run `voicelayer serve` to start the server.
EOF
# RIGHT — quoted delimiter: body is literal; backticks and $() preserved
cat >> notes.md <<'EOF'
Run `voicelayer serve` to start the server.
EOF2. Command Construction (CRITICAL)
- Use arrays for command arguments, never string concatenation
- Check for shell metacharacters before executing: `; && || | $( ) ``
# WRONG — metacharacter injection
cmd="git commit -m $message"
eval "$cmd"
# RIGHT — array construction
cmd=(git commit -m "$message")
"${cmd[@]}"3. Path Safety (HIGH)
- Always quote paths:
"$file_path"not$file_path - Use
realpathorreadlink -fto resolve symlinks - Check for path traversal: reject paths containing
..
4. Temporary Files (HIGH)
- Use
mktempnot hardcoded /tmp paths - Set
trap 'rm -f "$tmpfile"' EXITfor cleanup - Never write secrets to temp files
5. Error Handling (MEDIUM)
- Check return codes:
if ! command; then handle_error; fi - Use
|| trueonly when failure is genuinely acceptable - Log errors to stderr:
echo "ERROR: ..." >&2
5b. pipefail + SIGPIPE (CRITICAL with set -o pipefail)
- Piping into an early-exit consumer (
awk '{exit}',head -1,grep -m1) SIGPIPE-kills the producer → exit 141, often masked as success in naive scripts - Buffer first, consume second — write to a temp file or variable, then awk/grep/head the buffer
- Never use zsh read-only specials (
status,path, etc.) as variable names
# WRONG — producer gets SIGPIPE under pipefail
some_generator | awk '/pattern/{print; exit}'
# RIGHT — buffer then consume
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
some_generator > "$tmp"
awk '/pattern/{print; exit}' "$tmp"See /mac-systems mechanical truths #3–#4 for environment context.
6. ShellCheck (MEDIUM)
- Run
shellcheck script.shbefore committing - Fix all warnings, not just errors
- Suppress only with inline comments explaining why
Quick Audit Command
# Run on any script (covers section 6 — ShellCheck):
shellcheck --severity=warning script.sh && echo "CLEAN" || echo "ISSUES FOUND"Note: ShellCheck covers syntax and common pitfalls but does NOT detect sections 1-5 (injection patterns, path traversal, temp file misuse). Those require manual review using the checklist above.
Full SKILL.md source — includes LLM directives, anti-patterns, and technical instructions stripped from the Overview tab.
Apply to EVERY bash script before committing. The March 26 overnight sprint found security issues in every orchestrator worker PR (D-R1 through D-R6). Common patterns: JSON injection via printf, shell metacharacter bypass, unquoted paths, unquoted heredoc delimiters.
Mandatory Header
#!/usr/bin/env bash
set -euo pipefailset -e— exit on errorset -u— error on undefined variablesset -o pipefail— pipe failures propagate
Checklist
1. String Interpolation (CRITICAL)
- NEVER use
printfwith format strings to construct JSON — usejq - NEVER use
echo "$var"in command construction — use arrays - NEVER use
evalorbash -c "$string"with user input
# WRONG — JSON injection
printf '{"name": "%s"}' "$user_input"
# RIGHT — jq handles escaping
jq -n --arg name "$user_input" '{"name": $name}'1b. Heredoc Delimiters (CRITICAL when writing markdown)
- ALWAYS quote the heredoc delimiter (
<<'EOF') when writing or appending ANY content containing backticks or$()— markdown code spans count - Unquoted
<<EOFcommand-substitutes the body: every backtick span and$()EXECUTES
# WRONG — unquoted delimiter: the shell evaluates the backtick code span.
# Canonical incident (2026-06-07, PID 36248): writing exactly this kind of
# markdown note launched `voicelayer serve` against the default socket — the
# kickoff's ONE forbidden thing — while the live VoiceBar was in use.
cat >> notes.md <<EOF
Run `voicelayer serve` to start the server.
EOF
# RIGHT — quoted delimiter: body is literal; backticks and $() preserved
cat >> notes.md <<'EOF'
Run `voicelayer serve` to start the server.
EOF2. Command Construction (CRITICAL)
- Use arrays for command arguments, never string concatenation
- Check for shell metacharacters before executing: `; && || | $( ) ``
# WRONG — metacharacter injection
cmd="git commit -m $message"
eval "$cmd"
# RIGHT — array construction
cmd=(git commit -m "$message")
"${cmd[@]}"3. Path Safety (HIGH)
- Always quote paths:
"$file_path"not$file_path - Use
realpathorreadlink -fto resolve symlinks - Check for path traversal: reject paths containing
..
4. Temporary Files (HIGH)
- Use
mktempnot hardcoded /tmp paths - Set
trap 'rm -f "$tmpfile"' EXITfor cleanup - Never write secrets to temp files
5. Error Handling (MEDIUM)
- Check return codes:
if ! command; then handle_error; fi - Use
|| trueonly when failure is genuinely acceptable - Log errors to stderr:
echo "ERROR: ..." >&2
5b. pipefail + SIGPIPE (CRITICAL with set -o pipefail)
- Piping into an early-exit consumer (
awk '{exit}',head -1,grep -m1) SIGPIPE-kills the producer → exit 141, often masked as success in naive scripts - Buffer first, consume second — write to a temp file or variable, then awk/grep/head the buffer
- Never use zsh read-only specials (
status,path, etc.) as variable names
# WRONG — producer gets SIGPIPE under pipefail
some_generator | awk '/pattern/{print; exit}'
# RIGHT — buffer then consume
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
some_generator > "$tmp"
awk '/pattern/{print; exit}' "$tmp"See /mac-systems mechanical truths #3–#4 for environment context.
6. ShellCheck (MEDIUM)
- Run
shellcheck script.shbefore committing - Fix all warnings, not just errors
- Suppress only with inline comments explaining why
Quick Audit Command
# Run on any script (covers section 6 — ShellCheck):
shellcheck --severity=warning script.sh && echo "CLEAN" || echo "ISSUES FOUND"Note: ShellCheck covers syntax and common pitfalls but does NOT detect sections 1-5 (injection patterns, path traversal, temp file misuse). Those require manual review using the checklist above.
Best Pass Rate
91%
Sonnet 4.6
Assertions
11
3 models tested
Avg Cost / Run
$0.1337
across models
Fastest (p50)
2.8s
Haiku 4.5
Behavior Evals
Phase 2 baseline — skill quality on ClaudeBehavior Baseline
| Assertion | Opus 4.6 | Sonnet 4.6 | Haiku 4.5 | Consensus |
|---|---|---|---|---|
| identifies_injection | 3/3 | |||
| recommends_jq | 2/3 | |||
| flags_eval | 3/3 | |||
| suggests_arrays | 2/3 | |||
| identifies_missing_flags | 3/3 | |||
| recommends_pipefail | 0/3 | |||
| flags_hardcoded_tmp | 2/3 | |||
| flags_missing_trap | 3/3 | |||
| flags_secrets_in_tmp | 3/3 | |||
| identifies_unquoted | 3/3 | |||
| recommends_quoting | 3/3 |
Token Usage
Cost per Run
| Model | Input Tokens | Output Tokens | Cost / Run | Cost / 1K Runs |
|---|---|---|---|---|
| Opus 4.6 | 5,385 | 3,745 | $0.3616 | $361.60 |
| Sonnet 4.6 | 1,837 | 2,143 | $0.0377 | $37.70 |
| Haiku 4.5 | 957 | 1,292 | $0.0019 | $1.90 |
Response Time (p50)
Response Time (p95)
| Model | p50 | p95 | Overhead |
|---|---|---|---|
| Opus 4.6 | 8.8s | 13.0s | +48% |
| Sonnet 4.6 | 4.4s | 8.5s | +93% |
| Haiku 4.5 | 2.8s | 4.7s | +65% |
Last evaluated: 2026-03-12 · Data is generated from skill assertions (real cross-model benchmarks coming soon)
Changelog entries are derived from eval runs and skill version updates. Full cascading changelog (Phase 4D) coming soon.
Best Pass Rate
91%
Assertions
11
Models Tested
3
Evals Run
5
- +Initial release to Golems skill library
- +11 assertions across 5 eval scenarios