Files
telegram-web-proxy-stack/scripts/audit-public.sh
T

168 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
repository="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repository"
failures=0
report() {
printf 'FAIL: %s\n' "$1" >&2
failures=$((failures + 1))
}
scan_file() {
local label=$1 file=$2 pattern=$3
if LC_ALL=C grep -IqiE -- "$pattern" "$file"; then
report "$label in $file"
fi
}
collect_matches() {
local result_name=$1 label=$2 output status
shift 2
if output="$(LC_ALL=C "$@" 2>/dev/null)"; then
status=0
else
status=$?
fi
if ((status > 1)); then
report "$label could not complete"
output=''
fi
printf -v "$result_name" '%s' "$output"
}
scan_network_literals() {
local file=$1 context=$2 addresses domains address domain
collect_matches addresses "IPv4 scan for $context$file" \
grep -IhoE -- '([0-9]{1,3}\.){3}[0-9]{1,3}' "$file"
while IFS= read -r address; do
[[ -z "$address" ]] && continue
case "$address" in
127.0.0.1 | 0.0.0.0) ;;
*) report "unexpected IPv4 literal $address in $context$file" ;;
esac
done <<<"$(printf '%s\n' "$addresses" | sort -u)"
collect_matches domains "hostname scan for $context$file" \
grep -IhoE -- '([[:alnum:]][[:alnum:]-]*\.)+(com|org|net|ru|io|dev|app|cloud|info|biz|me|ai|local|localhost|de|uk|fr|us|ca|au|ch|nl|se|no|fi|pl|cz|kz|by|ua|su)([^[:alnum:]_.-]|$)' "$file"
while IFS= read -r domain; do
[[ -z "$domain" ]] && continue
domain="$(printf '%s' "$domain" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:].-]$//')"
case "$domain" in
github.com | core.telegram.org | example.com | *.example.com | *.localhost | \
*.md | *.sh | *.json | *.yml | *.yaml | *.html | *.go | *.js | \
*.gz | *.pem | *.key | *.p12 | *.pfx | *.example) ;;
*) report "unexpected hostname $domain in $context$file" ;;
esac
done <<<"$(printf '%s\n' "$domains" | sort -u)"
}
scan_local_denylist() {
local file=$1 context=$2 literal
[[ -f .audit-private-patterns ]] || return 0
while IFS= read -r literal || [[ -n "$literal" ]]; do
[[ -z "$literal" || "$literal" == \#* ]] && continue
if LC_ALL=C grep -IFqi -- "$literal" "$file"; then
report "locally denied identifier in $context$file"
fi
done < .audit-private-patterns
}
# The scanner contains signatures for credentials, so it is excluded only from
# those signature checks. Address, hostname and optional local deny-list checks
# still inspect it. Every tracked or untracked file not ignored by Git is read.
while IFS= read -r -d '' file; do
[[ -f "$file" ]] || continue
if [[ "$file" != scripts/audit-public.sh ]]; then
scan_file "private-key material" "$file" \
'-----BEGIN ([A-Z0-9]+ )?PRIVATE KEY-----'
scan_file "access-token pattern" "$file" \
'(ptr_|gh[pousr]_|github_pat_|glpat-|xox[baprs]-)[A-Za-z0-9_+/=-]{16,}'
scan_file "cloud credential pattern" "$file" \
'(AKIA|ASIA)[A-Z0-9]{16}|AIza[0-9A-Za-z_-]{35}'
scan_file "credential embedded in URL" "$file" \
'https?://[^/:[:space:]]+:[^/@[:space:]]+@'
scan_file "Bearer token" "$file" \
'Bearer[[:space:]]+[A-Za-z0-9._~+/-]{20,}={0,2}'
scan_file "32-hex client secret" "$file" \
'(^|[^0-9a-f])[0-9a-f]{32}([^0-9a-f]|$)'
scan_file "credential assignment" "$file" \
'(api[_-]?key|api[_-]?token|access[_-]?token|password|secret)[[:space:]]*[:=][[:space:]]*[^$[:space:]][A-Za-z0-9_+./=-]{15,}'
fi
scan_network_literals "$file" ''
scan_local_denylist "$file" ''
done < <(git ls-files --cached --others --exclude-standard -z)
while IFS= read -r -d '' file; do
case "$file" in
.env.example) ;;
.env | .env.* | */.env | */.env.* | *client-secret* | *profiles.json | \
*.pem | *.key | *.p12 | *.pfx | id_rsa | id_ed25519)
report "sensitive filename is tracked: $file"
;;
esac
done < <(git ls-files -z)
history_pattern='-----BEGIN ([A-Z0-9]+ )?PRIVATE KEY-----|(ptr_|gh[pousr]_|github_pat_|glpat-|xox[baprs]-)[A-Za-z0-9_+/=-]{16,}|(AKIA|ASIA)[A-Z0-9]{16}|AIza[0-9A-Za-z_-]{35}|https?://[^/:[:space:]]+:[^/@[:space:]]+@|Bearer[[:space:]]+[A-Za-z0-9._~+/-]{20,}={0,2}|(^|[^0-9a-f])[0-9a-f]{32}([^0-9a-f]|$)|(api[_-]?key|api[_-]?token|access[_-]?token|password|secret)[[:space:]]*[:=][[:space:]]*[^$[:space:]][A-Za-z0-9_+./=-]{15,}'
if revisions="$(git rev-list --all)"; then
:
else
report "Git history could not be enumerated"
revisions=''
fi
while IFS= read -r revision; do
[[ -z "$revision" ]] && continue
collect_matches matches "credential scan for Git history ${revision:0:12}" \
git grep -IilE -- "$history_pattern" "$revision" -- . \
':(exclude)scripts/audit-public.sh'
if [[ -n "$matches" ]]; then
while IFS= read -r match; do
report "sensitive content in Git history: $match"
done <<<"$matches"
fi
collect_matches addresses "IPv4 scan for Git history ${revision:0:12}" \
git grep -IhoE -- '([0-9]{1,3}\.){3}[0-9]{1,3}' "$revision" -- .
while IFS= read -r address; do
[[ -z "$address" ]] && continue
case "$address" in
127.0.0.1 | 0.0.0.0) ;;
*) report "unexpected IPv4 literal $address in Git history ${revision:0:12}" ;;
esac
done <<<"$(printf '%s\n' "$addresses" | sort -u)"
collect_matches domains "hostname scan for Git history ${revision:0:12}" \
git grep -IhoE -- '([[:alnum:]][[:alnum:]-]*\.)+(com|org|net|ru|io|dev|app|cloud|info|biz|me|ai|local|localhost|de|uk|fr|us|ca|au|ch|nl|se|no|fi|pl|cz|kz|by|ua|su)([^[:alnum:]_.-]|$)' "$revision" -- .
while IFS= read -r domain; do
[[ -z "$domain" ]] && continue
domain="$(printf '%s' "$domain" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:].-]$//')"
case "$domain" in
github.com | core.telegram.org | example.com | *.example.com | *.localhost | \
*.md | *.sh | *.json | *.yml | *.yaml | *.html | *.go | *.js | \
*.gz | *.pem | *.key | *.p12 | *.pfx | *.example) ;;
*) report "unexpected hostname $domain in Git history ${revision:0:12}" ;;
esac
done <<<"$(printf '%s\n' "$domains" | sort -u)"
done <<<"$revisions"
while IFS= read -r identity_email; do
[[ -z "$identity_email" || "$identity_email" == *noreply* ]] && continue
report "personal author or committer e-mail remains in Git history"
break
done < <(git log --all --format='%ae%n%ce' | sort -u)
while IFS= read -r identity_name; do
[[ -z "$identity_name" || "$identity_name" == Maintainer ]] && continue
report "non-generic author or committer name remains in Git history"
break
done < <(git log --all --format='%an%n%cn' | sort -u)
if ((failures > 0)); then
printf '\nPublic-release audit failed with %d finding(s).\n' "$failures" >&2
exit 1
fi
echo "Public-release audit passed: no secrets, deployment identifiers or unexpected address literals found."