#!/usr/bin/env bash set -uo pipefail # CASAN Plan-16 SEC-28 (X-04) — path-traversal / symlink guard. # # path-guard.sh resolves the REAL path (following symlinks, normalizing "..") and # refuses anything that escapes the allowed root — so a tool file argument cannot be # a symlink to /etc/passwd or a ../.. escape. Proves in-root paths pass and escapes # (via .. and via symlink) are rejected. # # Deterministic; hermetic. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PG="$PROJECT_ROOT/.specify/scripts/bash/path-guard.sh" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT PASS=0; FAIL=0 pass() { echo "PASS: $1"; PASS=$((PASS + 1)); } fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); } rc_of() { set +e; "$@" >/dev/null 2>&1; echo $?; set -e 2>/dev/null || true; } echo "===== Plan-16 SEC-28: path-traversal / symlink guard =====" mkdir -p "$WORK/root/sub" [[ "$(rc_of bash "$PG" "$WORK/root/sub/out.txt" "$WORK/root")" -eq 0 ]] \ && pass "in-root path accepted" || fail "in-root path rejected" [[ "$(rc_of bash "$PG" "$WORK/root/../../etc/passwd" "$WORK/root")" -ne 0 ]] \ && pass "'..' escape rejected" || fail "'..' escape accepted" # A symlink inside the root that points OUTSIDE it must be rejected. ln -s /etc/passwd "$WORK/root/evil-link" [[ "$(rc_of bash "$PG" "$WORK/root/evil-link" "$WORK/root")" -ne 0 ]] \ && pass "symlink escaping root rejected (realpath resolves the target)" \ || fail "symlink escape accepted" # A symlink that stays inside the root is fine. echo hi > "$WORK/root/sub/real.txt" ln -s "$WORK/root/sub/real.txt" "$WORK/root/ok-link" [[ "$(rc_of bash "$PG" "$WORK/root/ok-link" "$WORK/root")" -eq 0 ]] \ && pass "in-root symlink accepted" || fail "in-root symlink rejected" echo "" echo "===== SEC-28 SUMMARY: PASS=$PASS FAIL=$FAIL =====" [[ "$FAIL" -eq 0 ]] || exit 1