A coaching platform for wrestling programs. Coaches plan practices, manage a roster, track weigh-ins and results, and review match film. Athletes get a separate mobile app where they see their own schedule, their coach's feedback, and log their own weight.
Static pages and serverless functions on Netlify. Data, authentication and file storage on Supabase, which is managed PostgreSQL. There is no self-managed server.
Backups, stated precisely. Supabase takes managed backups of the database. Separately, a nightly job writes a per-program JSON snapshot to the owner's workstation, which is a full-disk-encrypted Mac (FileVault) in the owner's home, and keeps the last 28 days. Those snapshots contain roster rows, so they contain athlete names. They exist so a bad write can be undone within minutes rather than waiting on a support ticket, and one was used for exactly that on 2026-10-06. If your agreement requires that no copy of institutional data leave managed infrastructure, we will exclude your program from that job or move it to encrypted object storage; say which and it is a configuration change, not a rewrite.
| Service | Purpose | Receives |
|---|---|---|
| Netlify | Hosting, serverless functions | Request traffic |
| Supabase | Database, authentication, file storage | All application data |
| Stripe | Payments | Billing contact and payment details |
| Resend | Transactional email | Recipient email address, message body |
| Anthropic | AI practice and season generation, scouting text parsing | See "What reaches an AI model" below |
| Google Analytics 4 | Aggregate traffic on the PUBLIC MARKETING SITE ONLY | Anonymous page view data. It does not run on any signed-in page: not the coach application, not the athlete application. Removed from eleven application pages on 2026-10-07 so that the code matches this statement. In-product usage is measured from our own database rows instead, never by a third party. |
No card data touches Forge servers. Stripe collects and holds it directly.
First name, last name, email, phone, birth date, graduation year, gender, status, plus the wrestling facts a coach keeps: weight class, certified minimum weight, match results, attendance, and eligibility dates.
We do not hold, and the application has no field for: home address, Social Security number, transcripts, grades, medical history, insurance details, or any photo identification. Athletes cannot upload an image anywhere in the application.
The athlete profile page states this to athletes directly, so that a phishing attempt asking for those documents is recognizable as not coming from us.
An athlete record is created with the status pending_consent. In that state the record is invisible to every coach, including the coach who created it. It becomes visible only after a guardian opens a one-time tokenized link and gives consent. The consent timestamp and the originating IP are recorded.
Guardians do not need an account. There is no parent login to provision, deprovision or audit.
Under-18 athletes who join a team with a join code are routed through the same guardian consent step before activation. Recruiting visibility for a minor requires a separate, explicit guardian consent.
Authentication is Supabase Auth, and it is passwordless. A user enters an email address and receives a one-time code; Forge stores no passwords. Coaches and athletes use the same authentication system and separate application surfaces. The trade-off is stated under "What we do not have": there is no password to phish or reuse, and equally no second factor beyond possession of the mailbox.
Authorization is PostgreSQL row-level security, enforced in the database rather than in application code, on every table. A coach reaches only his own team's rows. An athlete reaches only his own.
Two properties of this design are worth stating plainly:
security definer database functions with a pinned search_path, not as policies, because a policy cannot make that distinction.Two buckets.
match-video is private. Access policies key on the team identifier as the first path segment, so a coach reads, uploads and deletes only under his own team's prefix. Maximum 2 GB, video MIME types only.
program-photos is public. It holds one branding image per program, uploaded by the coach. Maximum 8 MB, image MIME types only. No athlete data is stored in it.
Two features call Anthropic's API.
Season and practice generation sends only the coach's intake: sport, level, division, season dates, goals, team name, conditioning preference and tier. No roster, no athlete record and no athlete identifier is read from the database or included. This was verified field by field on 2026-10-05.
Scouting text parsing sends text that the coach pastes in himself, capped at 24,000 characters. That text may contain opponent names, because that is what a coach is pasting. Nothing is read from our database and sent.
Training. Anthropic's commercial terms state that Anthropic may not train models on customer content, inputs or outputs. Forge is a paid API customer and is on those terms. No athlete record is ever used to train a model, by us or by Anthropic.
Retention at Anthropic. Not training is a separate question from not retaining, and reviewers ask it next, so here is the honest answer. Anthropic holds API request and response data for a limited period for abuse monitoring under its standard commercial terms. Forge has not arranged zero-retention processing. We judge the exposure low because of the paragraphs above: the season call carries no athlete data at all, and the scouting call carries only text the coach chose to paste. If your institution requires zero retention for any third-party AI processing, tell us and we will take it up with Anthropic before you sign.
Turning it off. Both features are optional. A program that does not use the season builder or the scouting paste has no data leaving Forge for an AI model.
| Control | Setting |
|---|---|
| TLS | Enforced. Plain HTTP 301s to HTTPS. |
| HSTS | max-age=31536000; includeSubDomains |
| Framing | X-Frame-Options: SAMEORIGIN, and DENY on the athlete app, which holds minors' data |
| MIME sniffing | X-Content-Type-Options: nosniff |
| Referrer | strict-origin-when-cross-origin, so a path containing a team id never reaches a third party |
| Permissions | Camera, microphone, geolocation, payment, USB and motion sensors all denied. Forge asks for none of them. |
Third-party JavaScript. The application loads one third-party library at runtime, the Supabase client. It is pinned to an exact version and carries a SHA-384 Subresource Integrity hash, so a browser refuses to execute it if the CDN ever serves different bytes. Added 2026-10-07; before that it tracked the latest 2.x release, which is a supply chain exposure on pages that handle student data. No analytics or advertising script runs on any signed-in page.
Content-Security-Policy is not yet set. It is the one control on this list we have not shipped. The application embeds YouTube for film and the marketing site links to a dozen outside wrestling vendors, so a policy written without testing would break film playback for a paying coach. We would rather tell you it is missing than ship one that silently fails open.
Public links. Three pages are reachable without a login: a team's schedule and record for families, a single match video opened from a link a coach sends, and the unsubscribe page. They are capability URLs, meaning possession of the link is the authorisation. Two things bound that risk: the payload is deliberately team-level only, with no athlete names, weights or per-wrestler records, and that exclusion is enforced in the serverless function rather than in the page, so the data never reaches the browser. The honest limitation is that the identifier is the team's primary key, so the link cannot be rotated to revoke it. If you need a revocable link we will issue one; it is a schema change, not a redesign.
Rate limiting. Authentication throttling is Supabase's. Forge's own functions do not implement per-caller rate limits beyond the free build quotas on the season generator. Stated plainly because it is a fair question.
Every deploy runs an automated gate before anything reaches hosting. There is no override flag.
One check in that gate, qa/rls_qa.mjs, connects as an unauthenticated user and attempts to read every table and view in the database. Any returned row fails the deploy. This exists because of a real finding, below.
Database changes are additive only. Application code is written to work against both the previous and the new schema, so a partially applied change cannot lock a user out.
A third-party security review on 2026-09-12 found that plain database views bypassed row-level security, and that Supabase grants read access on new public objects by default. The reviewer retrieved billing and usage rows using the public API key.
Remediated the same day: views revoked from anonymous and authenticated roles and switched to security_invoker, default privileges revoked in the public schema, and schema files removed from the web root. The automated gate described above was added so that this class of defect cannot return silently. All findings from that review are closed.
Production has since been migrated entirely to Supabase's current key format. Legacy JWT-based API keys are disabled on the production project.
Automated checks run every 15 minutes against the public site, the application shell, a serverless function, the authentication endpoint and a live database read, with alerting on state change.
A single named owner, reachable at aaron@forgeathleticsystems.com. Forge is a small company and does not maintain a 24-hour on-call rotation. We will commit to a notification window in writing as part of any agreement.
On request we export a program's full data set and delete it, including stored video. Deleting an athlete's authentication account leaves the athlete record with a null user reference rather than orphaning related rows.
Database, file storage and backups sit in Supabase, which encrypts data at rest with AES-256 on AWS-managed volumes. Forge operates no storage of its own, so there is no second place to ask about, with the one exception named under Architecture: the nightly JSON snapshot on the owner's workstation, which rests on a FileVault-encrypted disk.
| Event | Where it is recorded | Kept |
|---|---|---|
| Sign-in, sign-out, one-time code issued and used | Supabase Auth logs | Per Supabase plan retention |
| Every serverless function invocation, with status | Netlify function logs | Per Netlify plan retention |
| Guardian consent for a minor | In the database, with timestamp and originating IP | For the life of the record |
| Coach approving an athlete's join request | In the database: who approved, when, and which roster row | For the life of the record |
What we do not have is a user-visible audit trail inside the product. An administrator cannot today open a screen and read who viewed what. If that is a requirement for your institution, say so during evaluation rather than after.
Program data is kept for as long as the account is active, because a coach's value in Forge is the multi-season history. There is no automatic expiry.
The attack surface is deliberately small: static pages, serverless functions, and one third-party library in the browser.
What we do not have is a scheduled third-party vulnerability scan or a formal patch SLA. For an application of this shape, the honest control is the small surface plus the release gate, and we would rather describe that than claim a process we do not run.
Stated plainly rather than left for a questionnaire to uncover:
Questions, or a data privacy agreement you need signed? Email aaron@forgeathleticsystems.com. We would rather sign yours than ask you to accept ours.