Files
php-security-linter/php-security-lint

243 lines
7.2 KiB
Plaintext
Raw Normal View History

#!/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] <path>
# 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] <path>
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 <format> Output format: text, json, html, sarif, markdown
-s, --severity <level> Minimum severity: low, medium, high, critical
-o, --output <file> Write output to file
-l, --lang <lang> Language: ja, en
-c, --context [N] Show N lines of code context (default: 3)
-e, --exclude <pattern> Exclude pattern (can be used multiple times)
-i, --include <pattern> Include pattern (overrides exclude)
-d, --recursive-depth <n> 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 "$@"