Security comes before any feature or deadline in how this app is built. This is what actually protects an account, a password, and the credentials behind them - not a policy statement, the real mechanisms, condensed from Tourney's own in-app security documentation.
Anything a visitor submits - a bracket name, a search term, any form field - is sent to the database separately from the instruction that uses it, never assembled into the query text itself. That separation, applied uniformly including the bulk-loading stored procedures, is what actually stops SQL injection; running something as a stored procedure isn't what provides the protection, the parameterization is.
The same principle covers AI-generated content: text the model produces is filtered before anyone sees it. Only a small set of formatting tags survives, and every HTML attribute is stripped, so a generated response can't introduce active code into a page - the same class of protection, applied to cross-site scripting instead of SQL.
Only the reverse proxy in front of the site has a port open to the world. The database has no network path in from outside the host at all, and the application server is reachable only through that proxy, never directly - there's nothing to find or connect to behind it, whatever an attacker tries.
Passwords go through scrypt, a deliberately slow, memory-hard one-way function, combined with a random per-account salt. What's stored is the output of that function, not the password and not a reversible encryption of it - there's no key and no procedure that turns it back. Signing in re-runs the same function on what you typed and compares the two results, so the site can confirm a password is correct without ever knowing what it is. An administrator can reset an account (replacing the stored value) but can't look one up.
The random salt means two accounts that happen to share a password produce completely different stored values, so a match can't be detected and precomputed cracking tables don't apply. Password-reset links follow the same rule: the link contains a single-use, time-limited random token, and only a hash of that token is stored - the email is the only usable copy, and reading the database directly yields nothing redeemable.
Every response carries instructions telling the browser how to defend itself, independent of the application's own code:
The AI worker that writes the daily recap gets a database account restricted to one table and, within it, only the columns it must write - participant names, emails, and phone numbers are outside its reach entirely, enforced by the database, not by convention (full detail, plus the mTLS handshake that authenticates that connection, is on the main engineering page).
Reaching a real player is a separate, additional gate on top of all of the above: the same five-check CI/CD pipeline (secrets scan, tests, lint, security-lint, dependency audit) covered on Engineering Platform, plus a human sign-off that only the exact commit just tested can be promoted - detail on both on the main engineering page, §06.