Shared resources — system templates, integration presets, global configuration — are a legitimate multi-tenancy pattern, but the absence of a schema-level distinction between shared and tenant-owned resources means any authenticated tenant can modify data visible to all other tenants. CWE-732 and OWASP A01 both apply. The business impact ranges from corrupted onboarding templates that affect every new signup, to shared integration presets poisoned with malicious data that other tenants then import into their workflows.
High because a tenant modifying a shared resource causes immediate, cascading impact on every other tenant that reads or references that resource.
Establish a schema-level distinction between system-owned and tenant-owned resources, then enforce it in every write path:
// schema.prisma
model Template {
id String @id
isSystem Boolean @default(false) // true = platform-owned, read-only for tenants
organizationId String? // null = system template
}
// API write handler
const template = await db.template.findFirst({ where: { id: params.id } })
if (template?.isSystem) {
return new Response('Cannot modify system templates', { status: 403 })
}
if (template?.organizationId !== session.user.organizationId) {
return new Response('Forbidden', { status: 403 })
}
For tenant customization, create a tenant-specific copy via a fork operation rather than mutating the shared original — store it with isSystem: false and the tenant's organizationId.
ID: saas-multi-tenancy.shared-resources.shared-resources-no-leak
Severity: high
What to look for: Identify resources that are intentionally shared across tenants — system templates, integration presets, global configuration, public asset libraries. For each shared resource type, verify that the sharing is read-only from the tenant's perspective and that tenants cannot modify shared resources in ways that affect other tenants. Also check that truly global resources (system-wide configs, platform defaults) are not erroneously using a tenant context that could leak.
Pass criteria: Enumerate all resource types in the schema. Shared resources are clearly typed as global (not tenant-owned) in the schema. At least 1 schema-level distinction between tenant-owned and global tables must exist, and code. Tenants can read shared resources but cannot write to or delete them without the action being scoped to their own copy. When a tenant "customizes" a shared resource, a tenant-specific copy is created rather than mutating the shared original.
Fail criteria: A tenant can modify a shared resource that is visible to all tenants (e.g., editing a global template that other tenants use). Shared resource metadata includes tenant-specific data that could be seen by other tenants. The distinction between "shared" and "tenant-owned" is not enforced at the database or API level.
Skip (N/A) when: No shared resource concept is detected — all resources are explicitly tenant-owned. Signal: no is_global, is_template, is_system, or similar flag on any resource table; no concept of system-level vs. tenant-level resources in the schema.
Detail on fail: Name the shared resource type and the leak vector. Example: "Integration templates table has no access control. POST /api/integrations/templates allows any authenticated user to update any template record, including system templates shared across all tenants."
Remediation: Protect shared resources at the database and API layer:
// Schema: distinguish shared vs tenant-owned
model Template {
id String @id
isSystem Boolean @default(false) // true = read-only for tenants
organizationId String? // null = system template
}
// API: prevent tenants from mutating system resources
const template = await db.template.findFirst({ where: { id: params.id } })
if (template?.isSystem) {
return new Response('Cannot modify system templates', { status: 403 })
}
// Also verify the non-system template belongs to the requesting tenant
if (template?.organizationId !== session.user.organizationId) {
return new Response('Forbidden', { status: 403 })
}