Security¶
Authentication & Access Control¶
Cloudflare Access (Zero Trust)¶
All application routes (except /api/health) are protected by Cloudflare Access.
How it works:
- User navigates to the application URL
- Cloudflare Access intercepts the request
- User authenticates via email OTP or Google
- Cloudflare issues a signed JWT
- JWT is validated by middleware in the application
Allowed users:
@luminarium.aiemail addresses@fastmarkets.comemail addresses
JWT Validation Middleware¶
The application validates Cloudflare JWTs in src/middleware.ts:
// Validates JWT on all routes except /api/health
export const config = {
matcher: ['/((?!api/health).*)'],
};
Why embedded keys?
The App Runner VPC connector blocks outbound internet access. Cloudflare public keys are fetched at build time and embedded in the application.
Network Security¶
Private Database¶
RDS SQL Server is in a private subnet:
- No public IP address
- Not accessible from the internet
- Only App Runner can reach port 1433 via VPC Connector
App Runner¶
- Runs in AWS managed VPC
- Connects to RDS via VPC Connector
- Only exposes port 3000 to Cloudflare
Secrets Management¶
GitHub Secrets¶
Used by CI/CD pipeline:
| Secret | Purpose |
|---|---|
AWS_CI_ROLE_ARN | OIDC role for AWS access |
DB_PASSWORD | RDS admin password |
CLOUDFLARE_* | Cloudflare API access |
AWS Secrets Manager¶
Future phases will store application secrets (API keys) in AWS Secrets Manager, accessed via App Runner's IAM role.
Coding Standards¶
See .rules/security.md for detailed security coding standards.
Key Principles¶
- Never commit secrets - Use environment variables
- Validate all input - Use Zod schemas at API boundaries
- Use Prisma - Prevents SQL injection
- React escapes by default - Prevents XSS
- Check authentication - Verify session on protected routes
- Check authorization - Verify user owns resources
Example: Input Validation¶
import { z } from 'zod';
const InputSchema = z.object({
name: z.string().min(1).max(100).trim(),
email: z.string().email().toLowerCase(),
});
const parsed = InputSchema.safeParse(body);
if (!parsed.success) {
return Response.json({ error: 'Invalid input' }, { status: 400 });
}
Security Checklist¶
When adding new features:
- [ ] Input validated with Zod
- [ ] No raw SQL queries (use Prisma)
- [ ] No
dangerouslySetInnerHTMLwith user content - [ ] Protected routes check authentication
- [ ] Resources check authorization (user owns resource)
- [ ] Secrets in environment variables, not code
- [ ] No sensitive data in logs
Reporting Security Issues¶
If you discover a security vulnerability:
- Do not open a public GitHub issue
- Contact Paul directly
- Provide details of the vulnerability
- Allow time for a fix before disclosure