This repository is public so that other festivals and nonprofits can learn from it. That means everything here is readable by anyone — including people looking for a way in. A few ground rules keep that safe.
Please do not open a public issue for a security problem.
Use GitHub's private reporting: Security → Advisories → Report a vulnerability
on this repository. If that is not available to you, email
lotus.festival@lacity.org with SECURITY in the subject line.
Please include what you found, how to reproduce it, and what an attacker could do with it. We will acknowledge within a few days. This is a volunteer-run nonprofit site, so please be patient, and please do not test against the live site in ways that degrade it for festival visitors — no load testing, no automated scanning, no mass form submissions.
Nothing in this repository is a credential. Every secret is read from an
environment variable at runtime, and .env* files are git-ignored.
| Value | Where it lives |
|---|---|
| Preview password | PREVIEW_PASSWORD env var |
| Preview cookie signing key | PREVIEW_SESSION_SECRET env var |
| Resend API key | RESEND_API_KEY env var |
| Turnstile secret | TURNSTILE_SECRET_KEY env var |
| Upstash Redis token | UPSTASH_REDIS_REST_TOKEN env var |
.env.example documents every variable with placeholder values only.
The staging gate at /team is a soft gate. It keeps the unfinished site out
of search results and away from casual visitors before launch. It is deliberately
low-friction and is not a substitute for real authentication:
- It is a single shared password with no per-user identity.
- Anyone who has the password can share it.
- It protects nothing sensitive — the gated pages are the future public site.
Do not put anything behind it that would actually hurt if it leaked. If this site ever needs real accounts (for example, a vendor portal where applicants can see their own submission), that requires a proper auth provider and a database, not this gate.
What the gate does do properly:
- Compares the password in constant time, so it cannot be guessed by timing.
- Issues a signed,
HttpOnly,SameSite=Lax,Securesession cookie — HMAC-SHA256 vianode:crypto, since Next.js 16 runsproxy.tsand Server Actions on the Node runtime — that cannot be forged withoutPREVIEW_SESSION_SECRET. - Rate limits attempts per IP.
- Serves
noindexon every gated page.
- A Content Security Policy, HSTS,
X-Content-Type-Options,X-Frame-Options: DENY, a strictReferrer-Policyand a locked-downPermissions-Policy(seenext.config.ts). - Every form input is validated with the same Zod schema on the client and again on the server. Server-side validation is the one that counts.
- Rate limiting on every write path.
- Honeypot field + submit-timing heuristic, with optional Cloudflare Turnstile.
- No database and no user accounts, so there is no SQL to inject and no session store to steal.
- Dependabot and
npm auditin CI, plus gitleaks secret scanning on every push.
- Change
PREVIEW_PASSWORD. - Generate your own
PREVIEW_SESSION_SECRET(openssl rand -base64 48). - Remove the Los Angeles Lotus Festival branding and photography — the MIT
license covers the code only. See
LICENSE.