Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Saas Multitenant Architecture

ASecurity

Arquitectura de aislamiento multi-tenant para un SaaS B2B desde cero — elige entre tenant_id compartido, schema por tenant o BD por tenant, aplica el checklist IDOR a cada query, y enlaza con auth, testing y billing multi-tenant. Úsalo al arrancar un SaaS nuevo o al auditar el aislamiento de uno existente.

2 stars
0 votes
0 copies
1 views
Added 9/19/2026
developmentpythongotestinggit

Works with

cli

Security Analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned 9/19/2026

$npx -y skills add fernando-delrio/saas-claude-toolkit --skill saas-multitenant-architecture --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Saas Multitenant Architecture?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Saas Multitenant Architecture
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/fernando-delrio-saas-multitenant-architecture/badge)](https://www.skillsdirectory.com/skills/fernando-delrio-saas-multitenant-architecture)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: saas-multitenant-architecture
description: Arquitectura de aislamiento multi-tenant para un SaaS B2B desde cero — elige entre tenant_id compartido, schema por tenant o BD por tenant, aplica el checklist IDOR a cada query, y enlaza con auth, testing y billing multi-tenant. Úsalo al arrancar un SaaS nuevo o al auditar el aislamiento de uno existente.
---

# Arquitectura multi-tenant — decisión de aislamiento

Esta skill nace de una auditoría de seguridad real sobre un SaaS multi-tenant en producción: 5 fugas de aislamiento (IDOR) encontradas y corregidas en un solo día. No viene de un libro — viene de bugs reales. Por eso el checklist de la sección 3 pesa tanto como la tabla de estrategias.

## 1. Las 3 estrategias de aislamiento

| Estrategia | Coste | Complejidad operativa | Garantía de aislamiento | Migrar a la siguiente |
|---|---|---|---|---|
| **`tenant_id` compartido** — una tabla, una columna `tenant_id` en cada fila | Bajo — un solo servidor, un solo schema | Baja — un pool de conexiones, una migración para todos | Depende 100% de que CADA query filtre por `tenant_id`. Un solo endpoint que lo olvide es una fuga (IDOR) | Media-alta — hay que particionar datos existentes por tenant |
| **Schema por tenant** — mismo servidor Postgres, un schema por cliente | Medio — mismo servidor, N schemas, N migraciones a aplicar (o un runner que itera) | Media — el aislamiento lo da Postgres, no tu código, pero conectar al schema correcto en cada request es una responsabilidad más | Aislamiento fuerte a nivel de motor; un bug de aplicación no cruza schemas | Baja — mover un tenant a su propia BD es un `pg_dump`/`pg_restore` de un solo schema |
| **BD por tenant** — aislamiento físico total | Alto — N bases de datos, N conexiones, N migraciones, backups por separado | Alta — cada tenant es una unidad operativa propia (monitorización, escalado, incidentes) | Total — un bug de aplicación no puede filtrar entre tenants ni por accidente | N/A — ya es el aislamiento máximo |

**Recomendación por defecto para un SaaS B2B pequeño/mediano: `tenant_id` compartido.** Es el que menos fricción operativa añade mientras el número de tenants y el tamaño de los datos son manejables, y el que más rápido se construye. El coste que paga a cambio es que el aislamiento vive enteramente en la disciplina de código — de ahí el checklist de la sección 3.

## 2. Cuándo NO usar el default

No uses `tenant_id` compartido cuando:
- **Compliance exige aislamiento físico** — sanidad (HIPAA), banca, o cualquier contrato que exija que los datos de un cliente nunca compartan disco/proceso con los de otro. Ahí vas directo a BD por tenant, sin importar el tamaño.
- **Un tenant es mucho más grande que el resto** y necesita rendimiento dedicado (su propio índice, su propio tuning, su propia ventana de mantenimiento) sin que un vecino ruidoso lo degrade. Esquema o BD por tenant para ese cliente concreto; el resto puede seguir en `tenant_id` compartido (modelo híbrido).

## 3. El checklist IDOR (la lección real)

**Regla:** toda query que recibe un `id` externo controlado por el cliente (path param, query param o campo del body) necesita una de estas dos cosas antes de tocar la BD:
1. `tenant_id` en el filtro de la query, o
2. Una validación explícita de que el recurso referenciado pertenece al tenant del usuario autenticado, antes de usarlo.

### Ejemplo anonimizado del bug real

Un endpoint recibe en el body el id de OTRO recurso relacionado (no el recurso principal de la URL) y lo usa sin validar que pertenece al mismo tenant que el usuario autenticado:

```python
# ❌ INCORRECTO — el id del body no se valida contra el tenant
@router.patch("/shift-changes/{change_id}/review")
def review_shift_change(change_id: int, body: ReviewInput, user: User = Depends(get_current_user)):
    change = db.query(ShiftChange).filter(
        ShiftChange.id == change_id,
        ShiftChange.tenant_id == user.tenant_id,   # el recurso PRINCIPAL sí está filtrado
    ).first()
    if not change:
        raise HTTPException(404)

    # body.reviewer_id llega del cliente y NO se valida contra el tenant.
    # Un usuario del Tenant A puede pasar el id de un empleado del Tenant B
    # y el sistema lo acepta como revisor válido.
    change.reviewer_id = body.reviewer_id
    db.commit()
```

```python
# ✅ CORRECTO — todo id que entra por el body se valida contra el tenant también
@router.patch("/shift-changes/{change_id}/review")
def review_shift_change(change_id: int, body: ReviewInput, user: User = Depends(get_current_user)):
    change = db.query(ShiftChange).filter(
        ShiftChange.id == change_id,
        ShiftChange.tenant_id == user.tenant_id,
    ).first()
    if not change:
        raise HTTPException(404)

    reviewer = db.query(Employee).filter(
        Employee.id == body.reviewer_id,
        Employee.tenant_id == user.tenant_id,      # el id del BODY también se valida
    ).first()
    if not reviewer:
        raise HTTPException(400, detail="Revisor no encontrado en este tenant")

    change.reviewer_id = reviewer.id
    db.commit()
```

**La fuga no está en el recurso principal de la URL** (ese casi siempre se filtra bien porque es el "camino feliz" que se prueba primero). **Está en los ids secundarios que llegan por el body o por query params** — se asume que si el usuario está autenticado, cualquier id que envíe es válido. No lo es: hay que probar que ese id también pertenece a su tenant.

### Checklist para cada endpoint nuevo
- [ ] ¿El recurso principal de la URL filtra por `tenant_id`?
- [ ] ¿Todo id que llega por el body se valida contra el tenant del usuario autenticado, no solo contra su propia tabla?
- [ ] ¿Todo id que llega por query params (filtros, ordenación por FK) se valida igual?
- [ ] ¿El test de regresión usa DOS tenants reales e intenta cruzar datos entre ellos? (ver `testing-multitenant.md`)

## 4. Siguientes pasos

- **`auth-multitenant.md`** — JWT claim vs resolución en BD, modelo de roles, patrón de superadmin cross-tenant.
- **`testing-multitenant.md`** — fixture de dos tenants reales, disciplina de verificación con `git stash`.
- **`billing-stripe.md`** — verificación de firma de webhook, relación tenant ↔ `customer_id`.

Attribution

fernando-delriofernando-delrio
View sourceSee grades on GitHubMore from fernando-delrio →
SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

285172 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10311 votes
View all in development →