Overview
Implement scope-based data isolation. User scope aggregates across programs, program scope is fully isolated, project scope exists within programs.
What it is
Three-level scope hierarchy (User → Program → Project) with strict data isolation between programs.
Why we use it
Data security, multi-tenant isolation, and clear boundaries between testing programs.
When to use
All data queries must include scope filters. Programs NEVER share data.
Key Features
- User scope for cross-program aggregation
- Program scope for full isolation
- Project scope within programs
- Scope enforcement in all queries
Quick Start
Scope-Aware Query
Always include scope in database queries.
// Scope hierarchy
User Scope → Aggregated view across all programs for a user
└── Program Scope → Independent testing program (isolated)
└── Project Scope → Validation effort within a program
// CRITICAL: Data NEVER crosses program boundaries
// Each program is fully isolated
// Example: Scope-aware query
import { prisma } from '@/lib/db';
// Get items for a specific program (scoped query)
const items = await prisma.item.findMany({
where: {
programId: currentProgramId, // Always filter by program
},
});Patterns
User Scope
Aggregate data across programs for a user.
// User Scope - Cross-program aggregation for dashboard
// User scope shows aggregated data across all programs
// the user has access to
export async function getUserDashboard(userId: string) {
// Get all programs user belongs to
const memberships = await prisma.programMember.findMany({
where: { userId },
include: { program: true },
});
// Aggregate stats across programs
const stats = await Promise.all(
memberships.map(async ({ program }) => ({
programId: program.id,
programName: program.name,
itemCount: await prisma.item.count({
where: { programId: program.id },
}),
activeProjects: await prisma.project.count({
where: { programId: program.id, status: 'active' },
}),
}))
);
return { userId, programs: stats };
}Program Scope
Fully isolated program data.
// Program Scope - Fully isolated data
// Each program is a complete silo
// NO data sharing between programs
export async function getProgramData(programId: string, userId: string) {
// First verify user has access to this program
const membership = await prisma.programMember.findUnique({
where: {
userId_programId: { userId, programId },
},
});
if (!membership) {
throw new AuthorizationError('Access denied to this program');
}
// All queries MUST filter by programId
const items = await prisma.item.findMany({
where: { programId }, // REQUIRED
});
const projects = await prisma.project.findMany({
where: { programId }, // REQUIRED
});
return { items, projects };
}Project Scope
Project data within program context.
// Project Scope - Within program context
// Projects exist within a program
// Project data inherits program isolation
export async function getProjectDetails(
projectId: string,
programId: string,
userId: string
) {
// Verify program access first
await verifyProgramAccess(userId, programId);
// Project must belong to the program
const project = await prisma.project.findFirst({
where: {
id: projectId,
programId, // Enforce program boundary
},
include: {
items: true,
feedback: true,
},
});
if (!project) {
throw new NotFoundError('Project not found in this program');
}
return project;
}Watch Out
Cross-program data leakage
Don't
// Cross-program data leakage - DANGEROUS
export async function getItem(itemId: string) {
// Missing program filter - can access any program's data!
const item = await prisma.item.findUnique({
where: { id: itemId },
});
return item; // Could be from a different program
}Do
// Properly scoped query
export async function getItem(itemId: string, programId: string) {
// Always include program in where clause
const item = await prisma.item.findFirst({
where: {
id: itemId,
programId, // Enforce program boundary
},
});
if (!item) {
throw new NotFoundError('Item not found');
}
return item;
}Missing scope filters on queries
Don't
// Missing scope filter on list query
export async function getAllFeedback() {
// Returns ALL feedback across ALL programs!
const feedback = await prisma.feedback.findMany({
orderBy: { createdAt: 'desc' },
});
return feedback;
}Do
// Properly scoped list query
export async function getProgramFeedback(programId: string) {
// Only returns feedback for the specified program
const feedback = await prisma.feedback.findMany({
where: { programId },
orderBy: { createdAt: 'desc' },
});
return feedback;
}- Incorrect scope inheritance
- Aggregation queries leaking data