← Back
Public Audit

Vote Integrity

Every security measure protecting every vote. Published openly because our Charter demands radical transparency.

Version 1.0 March 1, 2026 David Solomon, Founder
18
Active Measures
4
Security Layers
14
Validation Steps
0
Client-Side Trust

Our philosophy: No single point of failure can compromise a vote. Every submission passes through 14 independent server-side validation steps before it's recorded. The client is never trusted.

Defense in Depth

Vote integrity isn't one feature — it's the entire architecture. We enforce validation at four independent layers: client-side (browser), server-side (Edge Function), database (PostgreSQL constraints), and infrastructure (Cloudflare, TLS). An attacker would need to compromise all four layers simultaneously to submit a fraudulent vote.

This page documents every measure. We publish this because our Charter's Principle 4 (Radical Transparency) demands it — and because we believe transparency makes security stronger, not weaker.


The 14-Step Pipeline

Every vote submission is routed through a Supabase Edge Function that performs 14 sequential checks before any data touches the database. This runs server-side — it cannot be bypassed by modifying client code.

1

Authorization Header

Verifies the request includes a valid Bearer JWT token. Unauthenticated requests are rejected immediately.

2

Request Body Parsing

Parses and validates the JSON payload: battle ID, vote choice, explanation, behavioral metrics, CAPTCHA token, and honeypot field.

3

Honeypot Detection

Checks a hidden form field that humans never see. Bots that auto-fill all fields are silently rejected.

4

Required Field Validation

Ensures battle ID, vote choice, and CAPTCHA token are all present and non-empty.

5

CAPTCHA Verification (Cloudflare Turnstile)

Server-side cryptographic verification of the Turnstile token via Cloudflare's API. Proves a real browser controlled by a human generated this request.

6

Supabase Client Initialization

Creates a privileged service-role client for database operations and a user-scoped client for authentication verification.

7

User Authentication (JWT Validation)

Extracts and validates the user identity from the JWT. Expired or tampered tokens are rejected.

8

Account Age Check

Requires the account to be at least 30 seconds old. Prevents rapid account-cycling attacks.

9

Rate Limit Enforcement

Checks the user's daily vote count. Maximum 10 votes per day per user, enforced server-side.

10

Battle Existence Validation

Confirms the battle ID references a real battle in the database. Fabricated IDs are rejected.

11

Duplicate Vote Prevention

Queries for an existing vote from this user on this battle. One vote per user per battle, enforced at both application and database levels.

12

Vote Record Insert

Writes the vote with all metadata: choice, explanation, time on page, mouse movements, scroll depth, device fingerprint, IP address, and timestamp.

13

Rate Limit Counter Update

Increments the user's daily vote counter for ongoing rate limit enforcement.

14

Success Confirmation

Returns the vote ID. Only then does the client reveal which models generated each response.


All Security Measures

Every active and planned security measure, organized by what it protects against.

🔒

Cloudflare Turnstile CAPTCHA

Non-intrusive bot detection. Browser behavior analysis generates a cryptographic proof of humanity, verified server-side.

Client + Server
Active

Server-Side Edge Function (14-Step Pipeline)

All vote validation happens server-side. The client cannot bypass any check by modifying browser code.

Server
Active
🔢

JWT Authentication

Every request requires a valid, non-expired JSON Web Token. Anonymous voting is impossible.

Server
Active

Server-Side Rate Limiting

Maximum 10 votes per user per calendar day. Prevents any single account from disproportionately influencing results.

Server
Active
🚫

Duplicate Vote Prevention

Application-level check + database UNIQUE constraint on (user_id, battle_id). One vote per user per battle, enforced at two levels.

Server + Database
Active
👶

Account Age Gate

New accounts must wait before voting. Makes rapid account-cycling attacks impractical.

Server
Active
🎓

Honeypot Field

Invisible form field that catches bots. Humans never see it; bots that auto-fill everything are silently rejected.

Client + Server
Active
👁

Blind Voting

Model identities hidden until after the vote is recorded. Eliminates brand bias entirely.

Client
Active
📖

Mandatory Reading Timer (22s)

Vote buttons are disabled until the timer expires. Forces voters to actually read both responses before deciding.

Client
Active
📊

Behavioral Analytics

Collects time-on-page, mouse movements, and scroll depth. Used to identify bot-like patterns and suspicious voting behavior.

Client + Server
Active
📱

Device Fingerprinting

Browser-derived hash detecting multi-account abuse from the same device.

Client + Server
Active
🌐

IP Address Logging

Records voter IP for post-hoc fraud analysis. Detects vote farms (many accounts, same IP).

Server
Active
🛡

Row Level Security (RLS)

PostgreSQL-enforced access control. Users can only interact with their own data, regardless of application bugs.

Database
Active
🗃

Database Constraints

Foreign keys, UNIQUE constraints, CHECK constraints (vote_choice in A/B/tie, scroll_depth 0-100), and NOT NULL enforcement.

Database
Active
🌎

CORS Restriction

Edge Function only accepts requests from tsarena.ai. Third-party sites cannot submit votes.

Server
Active
🚩

Vote Integrity Flagging

Suspicious votes can be flagged and excluded from leaderboard calculations without deleting the audit trail.

Database
Active
🛡

Cloudflare DDoS Protection

Infrastructure-level protection against denial-of-service attacks. SSL/TLS encryption on all traffic.

Infrastructure
Active
📱

SMS Phone Verification

One phone number per voter via Twilio. Makes Sybil attacks (fake accounts) economically unviable. Achieves "one real human, one real vote."

Client + Server
Pending

Structural Constraints

Beyond application logic, PostgreSQL enforces structural rules that serve as the final safety net. Even if the Edge Function has a bug, these constraints prevent invalid data from being written:

  • UNIQUE (user_id, battle_id) — Prevents duplicate votes at the database level, independent of application checks.
  • FOREIGN KEY to battles(id) — Every vote must reference a real battle. Fabricated battle IDs are impossible.
  • FOREIGN KEY to auth.users(id) — Every vote must belong to a real authenticated user.
  • CHECK (vote_choice IN 'A', 'B', 'tie') — Only valid vote choices are accepted. No arbitrary strings.
  • CHECK (scroll_depth BETWEEN 0 AND 100) — Behavioral data must be within valid ranges.
  • NOT NULL on critical fields — Vote choice, user ID, battle ID, and reading time cannot be omitted.
  • Auto-increment trigger — Battle vote counts update automatically via PostgreSQL trigger, not application code.

Our Ongoing Promise

Security is not a feature we shipped — it's a practice we maintain. As threats evolve, so do our defenses. This page will be updated whenever we add, modify, or retire a security measure.

If you discover a vulnerability, please report it to david@trainingrun.ai. We take every report seriously.

We publish this because we believe transparency makes security stronger, not weaker. If our defenses can't withstand public scrutiny, they aren't strong enough. This is what Radical Transparency looks like in practice.