Security Best Practices
Security is not a feature you add at the end. These practices should be part of your workflow from day one.
Never expose API keys in frontend code
This is the most common security mistake. API keys, database credentials, and secrets must never appear in client-side code.
What happens: A secret hardcoded in a React component gets included in the JavaScript bundle sent to every visitor. Anyone with DevTools can find it.
What to do instead:
- Store secrets in environment variables on the server side
- Use Lyna's Secrets panel in project settings for server-only variables
- Prefix client-safe variables with
NEXT_PUBLIC_, but only for values designed to be public (like the Supabase anon key)
// WRONG - secret exposed to the browser
const stripe = new Stripe("sk_live_abc123...");
// RIGHT - secret only on the server
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);Pre-publish security scan
Lyna scans your project for exposed secrets before publishing. The scanner checks for:
Secret patterns (critical/high): Stripe keys (sk_live_, sk_test_), AWS access keys (AKIA...), AWS secret keys, Firebase API keys, private key blocks (-----BEGIN PRIVATE KEY-----), JWT secrets, hardcoded passwords, database connection strings (postgres://, mysql://, mongodb://), and generic secret assignments.
Frontend patterns: eval() usage, innerHTML assignment, document.write(), sensitive data in localStorage, unsanitized dangerouslySetInnerHTML, and hardcoded API endpoints.
Database security: RLS policies, table permissions, and auth configuration.
Fix all flagged issues before deploying.
Use Edge Functions for sensitive operations
Anything involving secret keys, third-party API calls, or sensitive business logic should run server-side.
Use Edge Functions for:
- Payment processing (Stripe webhooks, payment intents)
- Sending emails via third-party services
- External API calls requiring authentication
- Any computation involving secret keys
Example prompt:
"Create a Supabase Edge Function for Stripe webhooks. Verify the signature using STRIPE_WEBHOOK_SECRET, process checkout.session.completed events, and update subscription status in the database."
Row-Level Security (RLS)
RLS controls who can read and write data at the database level. Without policies, tables are either fully public or fully locked down.
The rules
- Enable RLS on every table. Ask the AI to do this when creating tables.
- Create explicit policies for every operation. Define who can SELECT, INSERT, UPDATE, and DELETE.
- Use
auth.uid()to scope policies to the authenticated user. - Test your policies. Access data as different users to verify.
Common patterns
Users can only read their own data:
CREATE POLICY "Users can view own data"
ON profiles FOR SELECT
USING (auth.uid() = user_id);Users can only update their own data:
CREATE POLICY "Users can update own data"
ON profiles FOR UPDATE
USING (auth.uid() = user_id);Public read, authenticated write:
CREATE POLICY "Public read access"
ON posts FOR SELECT
USING (true);
CREATE POLICY "Authenticated users can insert"
ON posts FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);Testing RLS
After setting up policies:
- Query as an unauthenticated user. Only public data should be visible.
- Query as an authenticated user. Only their data (or data they have access to) should show.
- Attempt writes that should be blocked. They should return empty results, not errors.
Gotcha
If your queries return empty results unexpectedly, check RLS first. Missing policies silently block access rather than throwing errors.
Environment secrets management
How Lyna handles secrets
The Secrets panel in project settings stores environment variables that are:
- Encrypted at rest
- Server-side only (unless prefixed with
NEXT_PUBLIC_) - Not included in client JavaScript bundles
- Not visible in the code editor or version control
Best practices
- Never commit
.envfiles to Git. Lyna handles this automatically, but add.env*to.gitignoreif working with GitHub. - Use separate keys for dev and production. Most services (Stripe, Supabase) provide test and live keys.
- Rotate keys if compromised. Update in the provider's dashboard and in Lyna's Secrets panel.
- Minimize secrets count. Each secret is an attack surface.
Authentication best practices
Use Supabase Auth
Lyna integrates natively with Supabase Auth. Use it. Rolling your own auth is one of the most error-prone things in web development.
Session management
- Validate sessions server-side for protected routes
- Implement session expiry and refresh token rotation
- Never store session tokens in localStorage. Supabase Auth uses cookies, which is correct for Next.js.
Protected routes
Make sure pages requiring auth check the session server-side:
"Add auth checks to all dashboard routes. If the user is not authenticated, redirect to login. Use server-side checks, not client-side only."
Input validation
Validate all user input server-side, even with client-side validation. Client validation is UX; server validation is the security boundary.
- Zod schemas for tRPC procedures and API routes
- Sanitize user content rendered as HTML to prevent XSS
- Validate file uploads: check MIME types and file sizes on the server
Additional measures
Content Security Policy
For production, add a CSP header to control which scripts, styles, and resources can load:
"Add a Content Security Policy header in Next.js middleware. Allow scripts and styles from our domain and Supabase, block inline scripts and external sources."
Rate limiting
- Rate limit login attempts
- Rate limit expensive API operations
- Supabase has built-in rate limiting for auth endpoints
HTTPS
All Lyna-deployed apps use HTTPS by default. Custom domains get SSL automatically. Never serve content over plain HTTP.
Security checklist
Before publishing:
- No API keys or secrets in frontend code
- RLS enabled on all Supabase tables with appropriate policies
- Auth checks on all protected routes (server-side)
- Secrets stored in Lyna's Secrets panel, not in code
- User input validated server-side
- File uploads validated for type and size
- Pre-publish security scan passes with no critical issues
- Sensitive operations use Edge Functions or server routes