Initial commit: PHP/Laravel Security Linter v1.0.0
A static security analysis tool for PHP and Laravel applications with recursive taint analysis capabilities. Features: - Comprehensive vulnerability detection (XSS, SQL Injection, Command Injection, Path Traversal, CSRF, Authentication issues) - Recursive taint analysis across function calls - Blade template analysis with context-aware XSS detection - Smart escape detection and escape bypass detection - Syntax highlighting in terminal output - Multi-language support (Japanese/English) - Docker support for easy deployment - Multiple output formats (text, JSON, HTML, SARIF, Markdown) - CI/CD integration ready (GitHub Actions, GitLab CI) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
242
php-security-lint
Executable file
242
php-security-lint
Executable file
@@ -0,0 +1,242 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user