Skip to content

leaderboard anti-cheat scan #356

leaderboard anti-cheat scan

leaderboard anti-cheat scan #356

name: leaderboard anti-cheat scan
# The detector runs inside PostgreSQL at :41 because its bounded history scan
# can legitimately exceed the backend's 10-second HTTP proxy budget. This :53
# responder fails closed when that database-native scan is stale, rebuilds the
# affected snapshots, and independently reads back counts. Permanent purge/ban
# remains a human decision. Public logs contain counts only, never identities.
on:
schedule:
- cron: "53 * * * *"
workflow_dispatch: {}
concurrency:
group: leaderboard-anticheat
cancel-in-progress: false
permissions:
contents: read
jobs:
scan:
runs-on: ubuntu-latest
env:
API: https://srctyff5.us-east.insforge.app/functions/tokentracker-leaderboard-refresh
steps:
- name: Verify scan, isolate, and refresh leaderboard
env:
REFRESH_SECRET: ${{ secrets.LEADERBOARD_REFRESH_SECRET }}
run: |
set -euo pipefail
if [[ -z "${REFRESH_SECRET:-}" ]]; then
echo "::error::LEADERBOARD_REFRESH_SECRET is not configured"
exit 1
fi
request_json() {
local stage="$1"
local method="$2"
local url="$3"
local data="${4:-}"
local response_file
local http_code=""
response_file=$(mktemp)
if [[ "$method" == "GET" ]]; then
if ! http_code=$(curl --fail-with-body -sS --max-time 120 \
--retry 2 --retry-delay 3 --retry-max-time 90 --retry-all-errors \
-o "$response_file" -w '%{http_code}' "$url"); then
echo "::error::$stage failed (HTTP ${http_code:-transport error})" >&2
jq -c '{error}' "$response_file" >&2 2>/dev/null || true
rm -f "$response_file"
return 1
fi
else
if ! http_code=$(curl --fail-with-body -sS --max-time 120 \
--retry 2 --retry-delay 3 --retry-max-time 90 --retry-all-errors \
-X "$method" "$url" \
-H "Content-Type: application/json" \
-H "x-refresh-secret: $REFRESH_SECRET" \
--data "$data" \
-o "$response_file" -w '%{http_code}'); then
echo "::error::$stage failed (HTTP ${http_code:-transport error})" >&2
jq -c '{error}' "$response_file" >&2 2>/dev/null || true
rm -f "$response_file"
return 1
fi
fi
cat "$response_file"
rm -f "$response_file"
}
health_before=$(request_json "health-before" GET "$API?anomalies=1")
if ! jq -e '
.ok == true and
(.auto_excluded | type == "number") and
(.review | type == "number") and
(.last_scan_completed_at | type == "string") and
((.last_queue_changed_at | type) == "string" or .last_queue_changed_at == null) and
((.last_response_completed_at | type) == "string" or .last_response_completed_at == null)
' >/dev/null <<<"$health_before"; then
echo "::error::anti-cheat health returned an invalid payload"
exit 1
fi
last_scan_completed_at=$(jq -r '.last_scan_completed_at' <<<"$health_before")
completed_epoch=$(date -u -d "$last_scan_completed_at" +%s)
now_epoch=$(date -u +%s)
scan_age_seconds=$(( now_epoch - completed_epoch ))
if (( scan_age_seconds < -300 || scan_age_seconds > 5400 )); then
echo "::error::database-native anti-cheat scan is stale (${scan_age_seconds}s old)"
exit 1
fi
excluded=$(jq -r '.auto_excluded' <<<"$health_before")
review=$(jq -r '.review' <<<"$health_before")
queue_changed_at=$(jq -r '.last_queue_changed_at // empty' <<<"$health_before")
response_completed_at=$(jq -r '.last_response_completed_at // empty' <<<"$health_before")
needs_response=false
if [[ -n "$queue_changed_at" ]]; then
queue_epoch=$(date -u -d "$queue_changed_at" +%s)
response_epoch=0
if [[ -n "$response_completed_at" ]]; then
response_epoch=$(date -u -d "$response_completed_at" +%s)
fi
if (( queue_epoch > response_epoch )); then
needs_response=true
fi
fi
week_rows=0
month_rows=0
total_rows=0
if [[ "$needs_response" == "true" ]]; then
for period in week month total; do
refreshed=$(request_json "refresh-$period" POST "$API" \
"{\"period\":\"$period\",\"force_refresh\":true,\"source\":\"github-actions-anticheat\"}")
jq -e --arg period "$period" \
'.ok == true and (.results[$period].upserted | type == "number")' \
>/dev/null <<<"$refreshed"
printf -v "${period}_rows" '%s' "$(jq -r --arg period "$period" '.results[$period].upserted' <<<"$refreshed")"
done
marker_payload=$(jq -cn --arg completed "$queue_changed_at" \
'{anti_cheat_response_completed_at: $completed}')
marked=$(request_json "mark-response-completed" POST "$API" "$marker_payload")
jq -e --arg completed "$queue_changed_at" '
.ok == true and .last_response_completed_at == $completed
' >/dev/null <<<"$marked"
response_completed_at="$queue_changed_at"
fi
health_after=$(request_json "health-after" GET "$API?anomalies=1")
jq -e \
--argjson excluded "$excluded" \
--argjson review "$review" \
--arg completed "$last_scan_completed_at" \
--arg queue_changed "$queue_changed_at" \
--arg response_completed "$response_completed_at" '
.ok == true and
.auto_excluded == $excluded and
.review == $review and
.last_scan_completed_at == $completed and
(.last_queue_changed_at // "") == $queue_changed and
(.last_response_completed_at // "") == $response_completed
' >/dev/null <<<"$health_after"
{
echo "### Leaderboard anti-cheat scan"
echo
echo "| state | count |"
echo "|---|---:|"
echo "| automatically excluded | $excluded |"
echo "| review-only flags | $review |"
echo
echo "- Database scan age: \`${scan_age_seconds}s\`"
echo "- Queue change required snapshot response: \`$needs_response\`"
echo "- Snapshot rows rebuilt (week/month/total): \`$week_rows/$month_rows/$total_rows\`"
echo "- Independent counts-only read-back: passed"
} >> "$GITHUB_STEP_SUMMARY"
if (( review > 0 )); then
echo "::warning::$review anomaly flag(s) require human review"
fi