Tenant settings frequently contain sensitive configuration: API keys, webhook secrets, custom domain names, and feature flag states. CWE-639 and OWASP A01 apply when settings are accessible by record ID without an organizationId check — an attacker who knows or can enumerate a settings UUID retrieves another tenant's configuration. For settings that contain integration credentials, this constitutes a direct credential disclosure event.
Low because exploiting the gap requires knowing a settings record UUID, which limits the attack surface, but successful exploitation exposes sensitive configuration data.
Always include the tenant scope in both fetch and update operations for settings records:
// Fetch — always scope to current org, never by bare ID
const settings = await db.organizationSettings.findFirst({
where: { organizationId: session.user.organizationId }
})
// Update — use updateMany with tenant scope so a mismatch returns count: 0
const updated = await db.organizationSettings.updateMany({
where: { id: params.id, organizationId: session.user.organizationId },
data: { ...payload }
})
if (updated.count === 0) return new Response('Not found', { status: 404 })
In src/app/api/settings/[id]/route.ts, remove any findUnique({ where: { id } }) pattern that does not include organizationId in the predicate. Settings cache keys must also include the tenant identifier (see cache-keys-include-tenant).
ID: saas-multi-tenancy.shared-resources.tenant-settings-isolated
Severity: low
What to look for: Examine tenant settings storage and retrieval — branding settings, feature flags, notification preferences, API configuration, custom domain settings. Check whether settings are stored per-tenant in the database and whether the settings retrieval always scopes to the requesting tenant. Look for patterns where settings are cached globally without a tenant key, or where a tenant can read or write another tenant's settings through the API.
Pass criteria: Count all settings tables. Tenant settings are stored with an explicit tenant foreign key. Settings must be scoped to at least 1 tenant identifier. Settings retrieval always filters by the requesting tenant's ID. Settings updates verify the requester owns the settings record being updated. No settings are cached without a tenant key.
Fail criteria: Tenant settings are stored in a table without a tenant key, relying on application-level logic to associate them. A tenant can read another tenant's settings by guessing or enumerating a settings record ID. Settings caching does not include the tenant in the cache key.
Skip (N/A) when: No tenant-level settings system is detected. Signal: no settings table, no per-tenant configuration storage, no settings API routes.
Detail on fail: Describe the settings isolation gap. Example: "GET /api/settings/[id] fetches settings by ID only without checking organizationId in src/app/api/settings/[id]/route.ts. Tenant B can read Tenant A's settings if they know the settings record UUID."
Remediation: Always scope settings operations to the current tenant:
// Fetch settings — always scope to current org
const settings = await db.organizationSettings.findFirst({
where: { organizationId: session.user.organizationId }
})
// Update settings — verify ownership
const updated = await db.organizationSettings.updateMany({
where: { id: params.id, organizationId: session.user.organizationId },
data: { ...payload }
})
if (updated.count === 0) return new Response('Not found', { status: 404 })