leaderboard anti-cheat scan #356
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |