# Tenant settings are isolated

- **Pattern:** `ab-002343` (`saas-multi-tenancy.shared-resources.tenant-settings-isolated`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002343
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002343)

## Why it matters

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.

## Severity rationale

Low because exploiting the gap requires knowing a settings record UUID, which limits the attack surface, but successful exploitation exposes sensitive configuration data.

## Remediation

Always include the tenant scope in both fetch and update operations for settings records:

```ts
// 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`).

## Detection

- **ID:** `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:

  ```ts
  // 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 })
  ```

---

## External references

- cwe CWE-285
- cwe CWE-639
- owasp A01

Taxons: access-control

HTML version: https://auditbuffet.com/patterns/ab-002343
