Newer
Older
ops-toolkit / packs / ops-scripts / bin / validate-paste.sh
#!/usr/bin/env bash
# validate-paste.sh -- pre-flight a bash script/paste block BEFORE running it.
#
# Mutates NOTHING. Checks a candidate script file for the failure modes that
# have actually bitten us:
#   1. bash -n parse errors (parse-only: passing this is NOT a correctness
#      guarantee -- behavior-test separately)
#   2. CR bytes (Windows line endings) -- counted via python byte count,
#      because grep for a CR literal false-positives on tokens containing $r
#   3. non-ASCII characters (silently corrupt some downstream consumers)
#   4. bare 'exit' outside a subshell wrapper -- kills the operator's login
#      shell when pasted (WARN: heuristic, review hits by eye)
#   5. common destructive commands with no visible gate nearby (WARN only)
#
# Usage: bash validate-paste.sh <file.sh>
# Exit codes: 0 PROCEED, 1 HOLD (hard check failed), 2 usage/tool missing.
# ASCII + LF.

set -uo pipefail
IFS=$'\n\t'

if [ $# -ne 1 ] || [ ! -f "${1:-}" ]; then
    echo "usage: validate-paste.sh <file.sh>" >&2
    exit 2
fi
TARGET=$1
HOLD=0

# --- 1. parse check -----------------------------------------------------------
if bash -n "$TARGET" 2>/tmp/vp-parse.$$; then
    echo "OK:   bash -n parse (parse-only, not a correctness guarantee)"
else
    echo "FAIL: bash -n parse errors:"
    sed 's/^/      /' /tmp/vp-parse.$$
    HOLD=1
fi
rm -f /tmp/vp-parse.$$

# --- 2. CR bytes (python byte count; grep on CR literals false-positives) ----
if command -v python3 >/dev/null 2>&1; then
    CRS=$(python3 -c 'import sys; print(open(sys.argv[1],"rb").read().count(b"\x0d"))' "$TARGET")
    if [ "$CRS" -eq 0 ]; then
        echo "OK:   no CR bytes (LF-only)"
    else
        echo "FAIL: $CRS CR byte(s) found -- Windows line endings; fix before use"
        HOLD=1
    fi
else
    echo "WARN: python3 not found; CR check skipped"
fi

# --- 3. non-ASCII -------------------------------------------------------------
NONASCII=$(grep -nP '[^\x00-\x7F]' "$TARGET" || true)
if [ -z "$NONASCII" ]; then
    echo "OK:   ASCII-only"
else
    echo "FAIL: non-ASCII characters found:"
    printf '%s\n' "$NONASCII" | head -5 | sed 's/^/      /'
    HOLD=1
fi

# --- 4. bare exit heuristic ---------------------------------------------------
# A paste block containing 'exit' at top level kills the operator's shell.
# Heuristic: flag files that use 'exit' but never open a subshell wrapper.
EXITS=$(grep -nE '(^|[;&| ])exit( |;|$)' "$TARGET" || true)
if [ -n "$EXITS" ] && ! grep -qE '^\(' "$TARGET"; then
    echo "WARN: 'exit' present with no obvious subshell wrapper '( { ...; } )'"
    echo "      If this is a PASTE block, a bare exit kills the login shell:"
    printf '%s\n' "$EXITS" | head -3 | sed 's/^/      /'
fi

# --- 5. destructive patterns --------------------------------------------------
DESTRUCTIVE=$(grep -nE '(^|[;&| ])(rm -rf|mkfs|dd if=|wipefs|sgdisk|parted) ' "$TARGET" || true)
if [ -n "$DESTRUCTIVE" ]; then
    echo "WARN: destructive command(s) present -- confirm each is individually gated:"
    printf '%s\n' "$DESTRUCTIVE" | head -5 | sed 's/^/      /'
fi

if [ "$HOLD" -eq 1 ]; then
    echo "FAIL: HOLD -- fix the failures above before pasting/running"
    exit 1
fi
echo "OK:   PROCEED (remember: behavior-test; syntax checks are not enough)"