Production-ready AI audit and governance platform for multi-tenant SaaS applications.
Relynt provides auditable, secure, multi-tenant logging and governance for AI-powered SaaS applications. This is a portfolio-grade application demonstrating senior-level understanding of:
- Multi-tenancy: Strict organization-based data isolation
- Security boundaries: Row Level Security (RLS) enforced at the database level
- Auditability: Immutable, append-only AI action logs
- Real-world SaaS patterns: Production-safe architecture
Frontend:
- Next.js 16 (App Router)
- TypeScript
- Server Components
- Tailwind CSS
Backend:
- Supabase (Postgres + Auth + RLS)
- Supabase Edge Functions (future)
AI:
- OpenAI-compatible interface (provider-agnostic)
- Wrapped with logging + guardrails
Every user belongs to one or more Organizations. All data is scoped to organization_id. The system enforces:
- Database-level isolation: RLS policies prevent cross-organization data access
- No client-side filtering: Security is enforced at the Postgres level
- Explicit policies: Every table has documented RLS policies
Organizations Table:
-- Users can only view organizations they are members of
CREATE POLICY "Users can view their organizations"
ON organizations FOR SELECT
USING (
id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid()
)
);AI Audit Logs Table:
-- CRITICAL: Users can ONLY view logs from their organizations
CREATE POLICY "Users can only view logs from their organizations"
ON ai_audit_logs FOR SELECT
USING (
organization_id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid()
)
);
-- Logs are IMMUTABLE - no UPDATE or DELETE policiesusers (Supabase Auth)
↓
organization_members
├─ organization_id → organizations
├─ user_id → users
└─ role (admin | member)
ai_audit_logs
├─ organization_id → organizations
├─ actor_id → users
├─ action
├─ input_summary
├─ output_summary
├─ risk_level (low | medium | high)
├─ metadata (JSONB)
└─ created_at (immutable)
❌ Wrong: Filtering data in React components
// INSECURE - data already leaked to client
const userLogs = allLogs.filter((log) => log.org_id === currentOrg);✅ Right: RLS at database level
-- Security enforced by Postgres, not JavaScript
CREATE POLICY ... USING (organization_id IN (...))❌ Wrong: Trusting client-provided org IDs
// INSECURE - attacker can change orgId
fetch(`/api/logs?orgId=${orgId}`);✅ Right: Server-side org resolution
// Server resolves org from authenticated user
const orgs = await getOrgsForUser(user.id);❌ Wrong: Allowing log updates/deletes
-- INSECURE - audit trail can be tampered with
CREATE POLICY ... FOR UPDATE ...✅ Right: Append-only logs
-- No UPDATE or DELETE policies = immutable
CREATE POLICY ... FOR INSERT ...- Database-enforced security: RLS policies prevent data leaks even if application code has bugs
- Immutable audit trail: Logs cannot be modified or deleted
- Deterministic risk detection: Rule-based (no AI hallucinations in security decisions)
- Explicit error handling: All edge cases handled with clear error messages
- Type safety: Shared Zod schemas between frontend and backend
- Monorepo structure: Single source of truth for data models
- Node.js 20+
- Supabase account
- npm or yarn
# Install dependencies (from root only)
npm install
# Set up environment variables
cp .env.example .env
# Edit .env with your Supabase credentials- Create a new Supabase project
- Run the migration:
# In Supabase SQL Editor, run: supabase/migrations/20260129_initial_schema.sql
# Start development server
npm run dev --workspace=web
# The app will be available at http://localhost:3000- Create an account at
/auth/signup - Provide organization name (you become admin)
- Go to
/simulate - Enter input/output summaries
- System automatically detects risk level
- Go to
/audit-logs - See all AI actions for your organization
- Logs are immutable and scoped to your org
Scenario: User A tries to access Org B's logs
- User A logs in (belongs to Org A)
- User A tries to query logs for Org B
- Result: RLS policy blocks the query - zero rows returned
Proof:
-- Even with direct SQL, User A cannot see Org B logs
SELECT * FROM ai_audit_logs WHERE organization_id = '<org-b-id>';
-- Returns: 0 rows (RLS blocks it)Risk levels are determined by deterministic rules (no AI):
- High: PII (SSN, credit cards), passwords, API keys
- Medium: Financial terms, payment info
- Low: Generic content
- Stripe billing integration
- Advanced filtering (date range, risk level)
- Export audit logs (CSV, JSON)
- Real-time alerts for high-risk actions
- Organization member management UI
relynt/
├── apps/
│ ├── web/ # Next.js frontend
│ └── api/ # Backend (future)
├── shared/ # Shared types/schemas
│ └── schemas/ # Zod schemas
├── supabase/
│ └── migrations/ # Database migrations
├── execution/ # Validation scripts
└── package.json # Root workspace config
IMPORTANT:
- Only root has
node_modules - Apps have
package.jsonbut NOnode_modules - All dependencies installed at root:
npm install
MIT
Built with production-grade practices. No shortcuts. No mock security.