Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:20:28 +08:00
commit b727790a9e
65 changed files with 16412 additions and 0 deletions

View File

@@ -0,0 +1,407 @@
#!/usr/bin/env bash
# ============================================================================
# Permission Checker - Audit file permissions for security issues
# ============================================================================
# Purpose: Detect world-writable files, overly permissive scripts, and permission issues
# Version: 1.0.0
# Usage: ./permission-checker.sh <path> <strict> <check_executables> <report_all>
# Returns: 0=all permissions correct, 1=issues found, 2=error
# ============================================================================
set -euo pipefail
# Source shared validation library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
if [[ -f "${PLUGIN_ROOT}/scripts/validate-lib.sh" ]]; then
source "${PLUGIN_ROOT}/scripts/validate-lib.sh"
fi
# ============================================================================
# Configuration
# ============================================================================
PATH_TO_SCAN="${1:-.}"
STRICT="${2:-false}"
CHECK_EXECUTABLES="${3:-true}"
REPORT_ALL="${4:-false}"
ISSUES_FOUND=0
declare -a FINDINGS=()
# ============================================================================
# Permission Classification
# ============================================================================
get_permission_octal() {
local file="$1"
stat -f "%Op" "${file}" 2>/dev/null | sed 's/.*\([0-7][0-7][0-7][0-7]\)$/\1/' || \
stat -c "%a" "${file}" 2>/dev/null || echo "0644"
}
get_permission_symbolic() {
local file="$1"
ls -ld "${file}" 2>/dev/null | awk '{print $1}' | tail -c 10
}
is_world_writable() {
local perms="$1"
[[ "${perms: -1}" =~ [2367] ]]
}
is_world_readable() {
local perms="$1"
[[ "${perms: -1}" =~ [4567] ]]
}
is_executable() {
local perms="$1"
[[ "${perms}" =~ [1357] ]]
}
# ============================================================================
# Severity Classification
# ============================================================================
get_issue_severity() {
local issue_type="$1"
local perms="$2"
case "${issue_type}" in
world_writable_executable)
echo "critical"
;;
world_writable)
echo "critical"
;;
missing_shebang)
echo "high"
;;
overly_permissive_sensitive)
echo "high"
;;
wrong_directory_perms)
echo "medium"
;;
non_executable_script)
echo "medium"
;;
inconsistent_perms)
echo "low"
;;
*)
echo "low"
;;
esac
}
# ============================================================================
# Shebang Validation
# ============================================================================
has_shebang() {
local file="$1"
if [[ ! -f "${file}" ]]; then
return 1
fi
local first_line
first_line=$(head -n 1 "${file}" 2>/dev/null || echo "")
[[ "${first_line}" =~ ^#! ]]
}
get_expected_shebang() {
local file="$1"
local basename
basename=$(basename "${file}")
case "${basename}" in
*.sh|*.bash)
echo "#!/usr/bin/env bash"
;;
*.py)
echo "#!/usr/bin/env python3"
;;
*.js)
echo "#!/usr/bin/env node"
;;
*.rb)
echo "#!/usr/bin/env ruby"
;;
*)
echo ""
;;
esac
}
# ============================================================================
# Expected Permissions
# ============================================================================
get_expected_permissions() {
local file="$1"
local basename
basename=$(basename "${file}")
local is_exec
# Check if currently executable
if [[ -x "${file}" ]]; then
is_exec="true"
else
is_exec="false"
fi
# Sensitive files
if [[ "${basename}" =~ ^\.env || "${basename}" =~ credentials || "${basename}" =~ secrets ]]; then
echo "600"
return
fi
# SSH/GPG files
if [[ "${file}" =~ \.ssh/id_ || "${file}" =~ \.gnupg/ ]]; then
if [[ "${basename}" =~ \.pub$ ]]; then
echo "644"
else
echo "600"
fi
return
fi
# Scripts
if [[ "${basename}" =~ \.(sh|bash|py|js|rb)$ ]]; then
if [[ "${is_exec}" == "true" ]] || has_shebang "${file}"; then
echo "755"
else
echo "644"
fi
return
fi
# Directories
if [[ -d "${file}" ]]; then
if [[ "${basename}" =~ ^\.ssh$ || "${basename}" =~ ^\.gnupg$ ]]; then
echo "700"
else
echo "755"
fi
return
fi
# Default
echo "644"
}
# ============================================================================
# Permission Checking
# ============================================================================
check_file_permissions() {
local file="$1"
local perms
perms=$(get_permission_octal "${file}")
local symbolic
symbolic=$(get_permission_symbolic "${file}")
local expected
expected=$(get_expected_permissions "${file}")
local basename
basename=$(basename "${file}")
# Skip certain directories
if [[ "${file}" =~ (\.git|node_modules|vendor|dist|build)/ ]]; then
return
fi
# CRITICAL: Check for 777 (world-writable and executable)
if [[ "${perms}" == "0777" || "${perms}" == "777" ]]; then
local issue_type="world_writable_executable"
local severity
severity=$(get_issue_severity "${issue_type}" "${perms}")
FINDINGS+=("${severity}|${file}|${perms}|${symbolic}|${expected}|World-writable and executable|Anyone can modify and execute|chmod ${expected} \"${file}\"")
((ISSUES_FOUND++))
return
fi
# CRITICAL: Check for 666 (world-writable)
if [[ "${perms}" == "0666" || "${perms}" == "666" ]]; then
local issue_type="world_writable"
local severity
severity=$(get_issue_severity "${issue_type}" "${perms}")
FINDINGS+=("${severity}|${file}|${perms}|${symbolic}|${expected}|World-writable file|Anyone can modify content|chmod ${expected} \"${file}\"")
((ISSUES_FOUND++))
return
fi
# Check if executable but missing shebang
if [[ -f "${file}" && -x "${file}" && "${CHECK_EXECUTABLES}" == "true" ]]; then
if [[ "${basename}" =~ \.(sh|bash|py|js|rb)$ ]]; then
if ! has_shebang "${file}"; then
local expected_shebang
expected_shebang=$(get_expected_shebang "${file}")
FINDINGS+=("high|${file}|${perms}|${symbolic}|${perms}|Executable without shebang|May not execute correctly|Add ${expected_shebang} to first line")
((ISSUES_FOUND++))
fi
fi
fi
# Check sensitive files
if [[ "${basename}" =~ ^\.env || "${basename}" =~ credentials || "${basename}" =~ secrets ]]; then
if is_world_readable "${perms}"; then
FINDINGS+=("high|${file}|${perms}|${symbolic}|600|Sensitive file world-readable|Secrets visible to all users|chmod 600 \"${file}\"")
((ISSUES_FOUND++))
return
fi
if [[ "${perms}" != "0600" && "${perms}" != "600" && "${STRICT}" == "true" ]]; then
FINDINGS+=("medium|${file}|${perms}|${symbolic}|600|Sensitive file should be 600|Reduce permissions|chmod 600 \"${file}\"")
((ISSUES_FOUND++))
return
fi
fi
# Strict mode: Check for any discrepancies
if [[ "${STRICT}" == "true" ]]; then
if [[ "${perms}" != "0${expected}" && "${perms}" != "${expected}" ]]; then
# Check if it's a minor discrepancy
if [[ "${perms}" =~ ^0?775$ && "${expected}" == "755" ]]; then
FINDINGS+=("medium|${file}|${perms}|${symbolic}|${expected}|Group-writable (strict mode)|Remove group write|chmod ${expected} \"${file}\"")
((ISSUES_FOUND++))
elif [[ "${perms}" =~ ^0?755$ && "${expected}" == "644" ]]; then
FINDINGS+=("low|${file}|${perms}|${symbolic}|${expected}|Executable but should not be|Remove executable bit|chmod ${expected} \"${file}\"")
((ISSUES_FOUND++))
fi
fi
fi
# Report all mode
if [[ "${REPORT_ALL}" == "true" ]]; then
if [[ "${perms}" == "0${expected}" || "${perms}" == "${expected}" ]]; then
FINDINGS+=("info|${file}|${perms}|${symbolic}|${expected}|Permissions correct|N/A|N/A")
fi
fi
}
# ============================================================================
# Main Execution
# ============================================================================
main() {
# Validate path
if [[ ! -e "${PATH_TO_SCAN}" ]]; then
echo "ERROR: Path does not exist: ${PATH_TO_SCAN}" >&2
exit 2
fi
echo "File Permission Audit Results"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Path: ${PATH_TO_SCAN}"
echo "Strict Mode: ${STRICT}"
echo "Check Executables: ${CHECK_EXECUTABLES}"
echo ""
# Scan files
local files_checked=0
if [[ -f "${PATH_TO_SCAN}" ]]; then
check_file_permissions "${PATH_TO_SCAN}"
((files_checked++))
elif [[ -d "${PATH_TO_SCAN}" ]]; then
while IFS= read -r -d '' file; do
check_file_permissions "${file}"
((files_checked++))
done < <(find "${PATH_TO_SCAN}" -print0 2>/dev/null)
fi
echo "Files Checked: ${files_checked}"
echo ""
# Report findings
if [[ ${ISSUES_FOUND} -eq 0 ]]; then
echo "✅ SUCCESS: All file permissions correct"
echo "No permission issues detected"
exit 0
fi
echo "⚠️ PERMISSION ISSUES DETECTED: ${ISSUES_FOUND}"
echo ""
# Group by severity
local critical_count=0
local high_count=0
local medium_count=0
local low_count=0
for finding in "${FINDINGS[@]}"; do
IFS='|' read -r severity file perms symbolic expected issue risk fix <<< "${finding}"
case "${severity}" in
critical) ((critical_count++)) ;;
high) ((high_count++)) ;;
medium) ((medium_count++)) ;;
low) ((low_count++)) ;;
info) ;; # Don't count info
esac
done
# Print findings by severity
if [[ ${critical_count} -gt 0 ]]; then
echo "CRITICAL Issues (${critical_count}):"
for finding in "${FINDINGS[@]}"; do
IFS='|' read -r severity file perms symbolic expected issue risk fix <<< "${finding}"
if [[ "${severity}" == "critical" ]]; then
echo "${file} (${perms})"
echo " Current: ${symbolic} (${perms})"
echo " Issue: ${issue}"
echo " Risk: ${risk}"
echo " Fix: ${fix}"
echo ""
fi
done
fi
if [[ ${high_count} -gt 0 ]]; then
echo "HIGH Issues (${high_count}):"
for finding in "${FINDINGS[@]}"; do
IFS='|' read -r severity file perms symbolic expected issue risk fix <<< "${finding}"
if [[ "${severity}" == "high" ]]; then
echo " ⚠️ ${file} (${perms})"
echo " Issue: ${issue}"
echo " Fix: ${fix}"
echo ""
fi
done
fi
if [[ ${medium_count} -gt 0 ]]; then
echo "MEDIUM Issues (${medium_count}):"
for finding in "${FINDINGS[@]}"; do
IFS='|' read -r severity file perms symbolic expected issue risk fix <<< "${finding}"
if [[ "${severity}" == "medium" ]]; then
echo " 💡 ${file} (${perms})"
echo " Recommendation: ${issue}"
echo " Fix: ${fix}"
echo ""
fi
done
fi
echo "Summary:"
echo " Critical: ${critical_count}"
echo " High: ${high_count}"
echo " Medium: ${medium_count}"
echo " Low: ${low_count}"
echo ""
if [[ ${critical_count} -gt 0 ]]; then
echo "Action Required: FIX IMMEDIATELY"
elif [[ ${high_count} -gt 0 ]]; then
echo "Action Required: YES"
else
echo "Action Required: REVIEW"
fi
exit 1
}
main "$@"