#!/usr/bin/env bash set -uo pipefail # CASAN Plan-16 SEC-28 (X-04) — path-traversal / symlink guard. # # A tool that takes a file path as input/output can be pointed at an arbitrary # location via `..` or a symlink (e.g. a symlink named "input.txt" -> /etc/passwd), # reading or writing outside the workspace. This resolves the REAL path (following # every symlink) and refuses anything that escapes the allowed root. # # Usage: path-guard.sh [allowed-root] (default root: repo workspace) # Exit: 0 inside the root, 1 outside / unresolvable. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/casan-paths.sh" PROJECT_ROOT="$CASAN_APP_ROOT" TARGET="${1:-}" ROOT="${2:-$PROJECT_ROOT}" if [[ -z "$TARGET" ]]; then echo "Usage: path-guard.sh [allowed-root]" >&2 exit 64 fi python3 - "$TARGET" "$ROOT" <<'PY' import os import sys target, root = sys.argv[1], sys.argv[2] # realpath resolves symlinks in every existing path component and normalizes ".."; # for a not-yet-created leaf it resolves the existing parent chain. real_target = os.path.realpath(target) real_root = os.path.realpath(root) if real_target == real_root or real_target.startswith(real_root + os.sep): print(f"PATH_OK {real_target}") sys.exit(0) sys.stderr.write(f"PATH_ESCAPES_ROOT target={target} real={real_target} root={real_root}\n") sys.exit(1) PY