#!/usr/bin/env bash
# Pre-release security scan for a BW plugin.
# Usage: tools/security-scan.sh bw-<slug>
#        tools/security-scan.sh --all

source "$(dirname "$0")/lib/common.sh"

SLUG="${1:-}"
ERRORS=0
WARNINGS=0

if [[ "$SLUG" == "--all" ]]; then
	log_info "Scanning all plugins..."
	rc=0
	for d in "${BW_PLUGINS_DIR}"/bw-*; do
		[[ -d "$d" ]] || continue
		"$0" "$(basename "$d")" || rc=$((rc + 1))
	done
	exit $rc
fi

require_slug "$SLUG"
PLUGIN_DIR="${BW_PLUGINS_DIR}/${SLUG}"
ALLOWLIST="${PLUGIN_DIR}/.security-allowlist"
SKIP_DIRS='/vendor/|/docs/|/tests/|/node_modules/|/\.git/'

is_allowlisted() {
	local pat="$1" loc="$2"
	[[ -f "$ALLOWLIST" ]] || return 1
	grep -q -F "${pat} ${loc}" "$ALLOWLIST" 2>/dev/null
}

scan() {
	local severity="$1" pattern="$2" label="$3"
	local findings
	findings=$(grep -rEn "$pattern" "$PLUGIN_DIR" --include="*.php" 2>/dev/null \
		| grep -vE "$SKIP_DIRS" || true)
	if [[ -n "$findings" ]]; then
		while IFS= read -r line; do
			local file=$(echo "$line" | cut -d: -f1)
			local lineno=$(echo "$line" | cut -d: -f2)
			local rel=$(realpath --relative-to="$PLUGIN_DIR" "$file"):${lineno}
			local snippet=$(echo "$line" | cut -d: -f3- | sed 's/^[[:space:]]*//' | cut -c1-80)
			if is_allowlisted "$label" "$rel"; then
				continue
			fi
			if [[ "$severity" == "ERROR" ]]; then
				log_error "[${label}] ${rel}: ${snippet}"
				ERRORS=$((ERRORS + 1))
			else
				log_warn "[${label}] ${rel}: ${snippet}"
				WARNINGS=$((WARNINGS + 1))
			fi
		done <<< "$findings"
	fi
}

log_info "Security scan: ${SLUG}..."

# Code execution
scan ERROR '\beval\s*\(' 'eval('
scan ERROR '\bassert\s*\(\s*\$' 'assert('
scan ERROR '\bbase64_decode\s*\(' 'base64_decode('
scan ERROR '\bsystem\s*\(' 'system('
scan ERROR '\bexec\s*\(' 'exec('
scan ERROR '\bshell_exec\s*\(' 'shell_exec('
scan ERROR '\bpassthru\s*\(' 'passthru('
scan ERROR '\bpopen\s*\(' 'popen('
scan ERROR '\bproc_open\s*\(' 'proc_open('
scan ERROR '`[^`]*\$' 'backtick exec'

# Dynamic includes with variables
scan ERROR '\binclude\s+\$' 'include $var'
scan ERROR '\brequire\s+\$' 'require $var'
scan ERROR '\binclude_once\s+\$' 'include_once $var'
scan ERROR '\brequire_once\s+\$' 'require_once $var'

# File ops on user input
scan ERROR 'file_get_contents\s*\(\s*\$_' 'file_get_contents($_)'
scan ERROR '\bfopen\s*\(\s*\$_' 'fopen($_)'

# extract on user input
scan ERROR 'extract\s*\(\s*\$_' 'extract($_)'

# SQL injection surface
scan ERROR '\$wpdb->query\s*\([^,)]*\.\s*\$' 'wpdb->query concat'
scan ERROR '\bmysqli_query\s*\(' 'mysqli_query (use $wpdb)'

# TLS verification disabled
scan ERROR 'CURLOPT_SSL_VERIFYPEER\s*,\s*(false|0)' 'CURLOPT_SSL_VERIFYPEER=false'
scan ERROR 'CURLOPT_SSL_VERIFYHOST\s*,\s*(false|0)' 'CURLOPT_SSL_VERIFYHOST=0'

# Committed secrets
scan ERROR "(api_key|apikey|api-key|secret|password|passwd|pwd|token)\s*[:=]\s*['\"][A-Za-z0-9+/=]{16,}['\"]" 'committed secret'
scan ERROR 'AKIA[0-9A-Z]{16}' 'AWS access key'
scan ERROR 'BEGIN (RSA |EC |DSA |OPENSSH |)PRIVATE KEY' 'PEM private key'
scan ERROR 'sk_live_[0-9a-zA-Z]{24,}' 'Stripe live key'
scan ERROR 'ghp_[0-9a-zA-Z]{36}' 'GitHub PAT'

# WARNING: raw superglobal access without sanitization
scan WARN '\$_(GET|POST|REQUEST|COOKIE)\[[^]]+\]\s*\)' 'raw $_GET/POST'

# Vendor integrity: verify plugin-update-checker present
if [[ ! -d "${PLUGIN_DIR}/vendor/plugin-update-checker" ]]; then
	log_warn "vendor/plugin-update-checker not present — plugin cannot self-update"
	WARNINGS=$((WARNINGS + 1))
fi

echo
if (( ERRORS > 0 )); then
	log_error "Security scan: ${ERRORS} error(s), ${WARNINGS} warning(s)"
	log_error "Fix the errors above, or add justified entries to ${SLUG}/.security-allowlist"
	exit 1
elif (( WARNINGS > 0 )); then
	log_warn "Security scan: clean (${WARNINGS} warning(s))"
	exit 0
else
	log_ok "Security scan: clean"
	exit 0
fi
