#!/usr/bin/env bash # # php-security-lint - PHP/Laravel Security Linter # # A wrapper script to run the security linter via Docker. # Install this script to /usr/local/bin/ for system-wide access. # # Usage: # php-security-lint [options] # php-security-lint app/ # php-security-lint -s high -f json . # # Installation: # curl -o /usr/local/bin/php-security-lint https://raw.githubusercontent.com/your-org/php-laravel-security-linter/main/php-security-lint # chmod +x /usr/local/bin/php-security-lint # set -e # Global variable to track container ID for cleanup CONTAINER_ID="" # Cleanup function to stop container on interrupt cleanup() { if [[ -n "$CONTAINER_ID" ]]; then docker stop "$CONTAINER_ID" >/dev/null 2>&1 || true fi echo "" exit 130 } # Trap SIGINT (Ctrl+C) and SIGTERM trap cleanup SIGINT SIGTERM # Configuration DOCKER_IMAGE="${PHP_SECURITY_LINT_IMAGE:-php-security-linter:latest}" CONTAINER_TARGET="/target" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Show usage show_usage() { cat << 'EOF' PHP/Laravel Security Linter Usage: php-security-lint [options] Examples: php-security-lint . # Scan current directory php-security-lint app/ # Scan app directory php-security-lint -s high . # Show only high+ severity php-security-lint -f json -o report.json . # Output as JSON Options: -f, --format Output format: text, json, html, sarif, markdown -s, --severity Minimum severity: low, medium, high, critical -o, --output Write output to file -l, --lang Language: ja, en -c, --context [N] Show N lines of code context (default: 3) -e, --exclude Exclude pattern (can be used multiple times) -i, --include Include pattern (overrides exclude) -d, --recursive-depth Recursive analysis depth (default: 10) --include-vendor Include vendor directory --include-tests Include tests directory --no-colors Disable colored output -q, --quiet Suppress progress output --verbose Show detailed information -h, --help Show this help message --version Show version Environment Variables: PHP_SECURITY_LINT_IMAGE Docker image to use (default: php-security-linter:latest) EOF } # Check if Docker is available check_docker() { if ! command -v docker &> /dev/null; then echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" >&2 echo "Please install Docker: https://docs.docker.com/get-docker/" >&2 exit 1 fi if ! docker info &> /dev/null; then echo -e "${RED}Error: Docker daemon is not running${NC}" >&2 echo "Please start the Docker daemon" >&2 exit 1 fi } # Check if image exists, build if necessary check_image() { if ! docker image inspect "$DOCKER_IMAGE" &> /dev/null; then echo -e "${YELLOW}Docker image '$DOCKER_IMAGE' not found.${NC}" # Check if we're in the project directory with Dockerfile if [[ -f "Dockerfile" ]] && grep -q "security-linter" "Dockerfile" 2>/dev/null; then echo "Building image from local Dockerfile..." docker build -t "$DOCKER_IMAGE" . else echo -e "${RED}Error: Docker image '$DOCKER_IMAGE' not found.${NC}" >&2 echo "" >&2 echo "To build the image, run from the security-linter directory:" >&2 echo " docker build -t php-security-linter:latest ." >&2 echo "" >&2 echo "Or pull from registry (if published):" >&2 echo " docker pull your-org/php-security-linter:latest" >&2 exit 1 fi fi } # Parse arguments and find target path parse_args() { local args=() local target_path="" local output_file="" local has_output=false while [[ $# -gt 0 ]]; do case "$1" in -h|--help) show_usage exit 0 ;; --version) docker run --rm "$DOCKER_IMAGE" --version exit 0 ;; -o|--output) has_output=true output_file="$2" args+=("$1" "$CONTAINER_TARGET/$(basename "$2")") shift 2 ;; -f|--format|-s|--severity|-l|--lang|-e|--exclude|-i|--include|-d|--recursive-depth) args+=("$1" "$2") shift 2 ;; -c|--context) # Handle optional numeric argument (must be a number, not a path) if [[ -n "$2" ]] && [[ "$2" =~ ^[0-9]+$ ]] && [[ ! "$2" =~ / ]]; then args+=("$1" "$2") shift 2 else args+=("$1") shift fi ;; --include-vendor|--include-tests|--no-colors|--no-default-excludes|--show-excluded|-q|--quiet|--verbose) args+=("$1") shift ;; -*) args+=("$1") shift ;; *) # This is the target path if [[ -z "$target_path" ]]; then target_path="$1" else args+=("$1") fi shift ;; esac done # Default to current directory if no path specified if [[ -z "$target_path" ]]; then target_path="." fi # Convert to absolute path if [[ "$target_path" == "." ]]; then TARGET_HOST_PATH="$(pwd)" TARGET_CONTAINER_PATH="$CONTAINER_TARGET" elif [[ "$target_path" == /* ]]; then TARGET_HOST_PATH="$target_path" TARGET_CONTAINER_PATH="$CONTAINER_TARGET" else TARGET_HOST_PATH="$(pwd)" TARGET_CONTAINER_PATH="$CONTAINER_TARGET/$target_path" fi DOCKER_ARGS=("${args[@]}" "$TARGET_CONTAINER_PATH") OUTPUT_FILE="$output_file" HAS_OUTPUT="$has_output" } # Main execution main() { check_docker check_image parse_args "$@" # Build docker run command local docker_cmd=( docker run --rm --init # Proper signal handling (Ctrl+C) ) # Allocate TTY and interactive mode if terminal is interactive if [[ -t 0 ]] && [[ -t 1 ]]; then docker_cmd+=(-it) elif [[ -t 1 ]]; then docker_cmd+=(-t) fi docker_cmd+=(-v "$TARGET_HOST_PATH:$CONTAINER_TARGET:ro") # If output file specified, we need write access to output directory if [[ "$HAS_OUTPUT" == "true" ]] && [[ -n "$OUTPUT_FILE" ]]; then local output_dir output_dir="$(dirname "$(pwd)/$OUTPUT_FILE")" mkdir -p "$output_dir" docker_cmd+=(-v "$output_dir:$CONTAINER_TARGET:rw") fi # Add image and arguments docker_cmd+=("$DOCKER_IMAGE" "${DOCKER_ARGS[@]}") # Execute "${docker_cmd[@]}" local exit_code=$? # Copy output file if specified if [[ "$HAS_OUTPUT" == "true" ]] && [[ -n "$OUTPUT_FILE" ]] && [[ -f "$OUTPUT_FILE" ]]; then echo -e "${GREEN}Report written to: $OUTPUT_FILE${NC}" fi exit $exit_code } main "$@"