Esta es una lista de comprobación concisa y centrada en producción para proteger aplicaciones SaaS multiinquilino. Es deliberada, prioriza el código y las defensas que reducen el riesgo de forma medible.
Lista de comprobación TL;DR
- Identidad: SSO/OIDC, MFA, passwordless cuando sea posible
- Sesiones: Secure+HttpOnly+SameSite, duraciones cortas, refresh tokens rotados
- Autorización: delimitación explícita por tenant + RBAC/ABAC; denegación por defecto
- Aislamiento de tenants: org_id en todas partes, DB RLS o aislamiento a nivel de servicio
- Secretos: almacén gestionado + rotación; no incluir secretos en git
- HTTPS + HSTS; encabezados de seguridad estrictos; CSP (nonces o hashes)
- CSRF en rutas que cambian el estado; CORS estricto en APIs
- Límites de velocidad por IP y por cuenta; controles contra bots y abuso
- Protección de datos: cifrado en reposo + a nivel de campo cuando sea necesario; KMS adecuado
- Cargas de archivos: detección MIME, límites de tamaño, análisis AV, URLs firmadas, buckets privados
- Webhooks: firmas HMAC, idempotencia, reintentos con jitter, privilegio mínimo
- Observabilidad: logs de auditoría estructurados, alertas de anomalías, almacenamiento con evidencia de manipulación
- Cadena de suministro: dependencias fijadas, SBOM, SCA/SAST, análisis de secretos en CI
- Cloud/IAM: privilegio mínimo, límites, acceso de emergencia, aislamiento sólido por entorno
- DR/BCP: copias de seguridad, restauraciones probadas, RPO/RTO definidos, claves respaldadas por HSM/KMS
1) Identidad y acceso (SSO/OIDC + MFA)
Prefiere la identidad federada (OIDC/SAML) con MFA obligatorio. Si ofreces autenticación local, exige contraseñas seguras y perfilado progresivo.
Ejemplo de OIDC (pseudo‑configuración):
oidc:
issuer: "https://accounts.example-idp.com"
client_id: "YOUR_CLIENT_ID"
client_secret: "${OIDC_CLIENT_SECRET}" # from secret store
redirect_uris:
- "https://app.example.com/auth/callback"
scopes: ["openid", "profile", "email", "offline_access"]
prompt: "login" # force fresh auth for sensitive actions
max_age: 300 # re-auth window for step-up operations
Mejores prácticas:
- Exige MFA en el IdP o mediante la política de la aplicación para roles de administrador/privilegiados
- Access tokens de corta duración (5–15 min), rota refresh tokens y vincula los tokens al cliente
- Almacena listas de revocación; finaliza las sesiones cuando cambien la contraseña o la 2FA
2) Autorización y delimitación por tenant
La autorización es donde ocurren la mayoría de las filtraciones de datos de SaaS. Protege todo acceso a datos con una delimitación explícita por tenant y políticas de denegación por defecto.
- Usa
tenant_id/org_idde forma coherente en cada tabla y límite de API - Aplica RBAC/ABAC con nombres de permisos claros; evita comprobaciones ad hoc de “is_admin” distribuidas por el código
- Añade cláusulas de protección al inicio de las solicitudes para comprobar la pertenencia al tenant + el permiso
Ejemplo de Row Level Security (RLS) de Postgres:
-- Enable RLS
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
-- Current tenant is set per session/connection:
-- SELECT set_config('app.current_tenant', 'acme', true);
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.current_tenant')::uuid);
-- Optional: restrict writes similarly
CREATE POLICY tenant_isolation_write ON invoices
FOR INSERT WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Alternativa de aislamiento a nivel de servicio:
- Una db dedicada por tenant para clientes de alto riesgo
- O al menos un schema dedicado; sopesa la complejidad operativa frente al radio de impacto
3) Secretos y configuración
- Usa un almacén de secretos gestionado (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, 1Password Connect)
- Rota las claves/tokens según un calendario y cuando el personal se marche
- Nunca registres secretos; usa logs estructurados con una lista de elementos permitidos
.env (solo para desarrollo):
OIDC_CLIENT_SECRET="change-me"
STRIPE_WEBHOOK_SECRET="whsec_..."
DATABASE_URL="postgresql://user:pass@localhost:5432/app"
4) HTTPS, HSTS y encabezados de seguridad
Establece encabezados estrictos en toda la plataforma. Para aplicaciones de navegador, exige CSP con nonces o hashes.
Ejemplo (pseudo‑middleware genérico):
// Express-style example
app.use((req, res, next) => {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
res.setHeader("Referrer-Policy", "no-referrer");
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
// CSP with nonce (attach to res.locals.nonce earlier)
res.setHeader(
"Content-Security-Policy",
`default-src 'self'; script-src 'self' 'nonce-${res.locals.nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:`
);
next();
});
5) Sesiones y cookies
- Usa cookies Secure+HttpOnly; SameSite=Lax o Strict para aplicaciones same-site
- Mantén las sesiones cortas, rota los ID de sesión cuando cambien los privilegios y al iniciar sesión
- Considera sesiones del lado del servidor (Redis) para la revocación y el control centralizado
Configuración de cookies (independiente del framework):
{
"SESSION_COOKIE_SECURE": true,
"SESSION_COOKIE_HTTPONLY": true,
"SESSION_COOKIE_SAMESITE": "Lax",
"PERMANENT_SESSION_LIFETIME_MIN": 0
}
6) CSRF y CORS
- Protege contra CSRF todos los métodos inseguros (POST/PUT/PATCH/DELETE) en flujos de cookies del navegador
- CORS: restringe a orígenes conocidos; no uses comodines con credenciales
CORS estricto (ejemplo):
const allowed = new Set(["https://app.example.com"]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowed.has(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Vary", "Origin");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader(
"Access-Control-Allow-Headers",
"authorization, content-type, x-request-id"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, OPTIONS"
);
}
if (req.method === "OPTIONS") return res.status(204).end();
next();
});
7) Límites de velocidad y prevención del abuso
- Límite global (por IP) más límites para rutas sensibles (inicio de sesión, restablecimiento de contraseña, registro)
- Cuotas por cuenta/tenant para evitar el abuso del vecino ruidoso
- Añade proof‑of‑work o CAPTCHA solo cuando sea necesario; mide primero
Ejemplo de limitador:
// Pseudo-code
limiter.global("200/min");
limiter.route("/auth/login", "5/min");
limiter.route("/password/reset", "5/min");
limiter.keyFunc((req) => req.ip + ":" + req.user?.tenant_id);
8) Protección de datos y cifrado
- Cifra en reposo (valor predeterminado de la nube) y en tránsito (TLS 1.2+)
- Cifrado a nivel de campo para PII/PHI/PCI de alta sensibilidad (almacena solo ciphertext; claves en KMS/HSM)
- Separa las claves KMS por entornos/tenants con estrictas listas de elementos permitidos en IAM
Cifrado a nivel de campo (pseudo):
from cryptography.fernet import Fernet
def encrypt_field(value: str, key: bytes) -> str:
return Fernet(key).encrypt(value.encode()).decode()
def decrypt_field(token: str, key: bytes) -> str:
return Fernet(key).decrypt(token.encode()).decode()
9) Límites de Cloud/IAM
- Separa proyectos/cuentas por entorno (prod frente a staging y dev)
- IAM con privilegio mínimo para aplicaciones y CI; deniega por defecto; credenciales de corta duración
- Usa perímetros de VPC/servicio cuando sean compatibles; controla la salida hacia endpoints aprobados
Ejemplo de política IAM (subconjunto de bucket de solo lectura):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::customer-uploads",
"arn:aws:s3:::customer-uploads/*"
],
"Condition": {
"StringEquals": { "aws:PrincipalTag/role": "uploader" }
}
}
]
}
10) Webhooks e integraciones salientes
- Verifica las firmas (HMAC o Ed25519) antes de procesar
- Exige claves de idempotencia; reintenta con retroceso exponencial + jitter
- Usa salida permitida mediante listas y DNS pinning cuando sea viable para flujos críticos
Verificación de webhook (ejemplo HMAC):
import hmac, hashlib, time
def verify_webhook(raw_body: bytes, signature: str, secret: bytes, tolerance_s: int = 300) -> bool:
# signature format: t=timestamp,v1=hex
parts = dict(p.split("=", 1) for p in signature.split(","))
ts = int(parts["t"])
if abs(time.time() - ts) > tolerance_s:
return False
expected = hmac.new(secret, f"{ts}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Idempotencia (pseudo):
CREATE TABLE webhook_events (
id TEXT PRIMARY KEY,
received_at TIMESTAMPTZ DEFAULT now(),
payload JSONB
);
-- INSERT ... ON CONFLICT DO NOTHING to avoid duplicates
11) Cargas de archivos
- Exige límites de tamaño, listas permitidas de extensiones y detección del tipo de contenido (no solo de los encabezados)
- Almacena en buckets privados; sirve mediante URLs firmadas de corta duración
- Análisis AV opcional para cargas no confiables; elimina los metadatos (EXIF)
Patrón de URL firmada:
// Pseudo: issue a short-lived upload URL; never expose bucket credentials
const url = storage.createSignedUrl({
bucket: "customer-uploads",
key: `tenants/${tenantId}/${randomUuid()}.bin`,
expiresInSeconds: 300,
contentType: "application/octet-stream",
});
12) Observabilidad, auditoría y alertas
- Emite logs JSON estructurados; incluye
tenant_id,user_id,ip,action,object,result - Sink de logs inmutable/de auditoría (WORM/S3 Object Lock) para eventos de seguridad críticos
- Alertas en tiempo real sobre anomalías de autenticación, denegaciones de permisos y exportaciones/eliminaciones masivas
Ayudante de auditoría (pseudo):
import json, logging, sys
logger = logging.getLogger("audit")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(handler)
def audit(event: str, **kwargs):
logger.info(json.dumps({"event": event, **kwargs}))
13) Seguridad de la cadena de suministro
- Fija las dependencias; mantén una SBOM; ejecuta SCA (por ejemplo,
pip-audit,npm audit,osv-scanner) - SAST/linters para clases comunes de errores; versiones/artefactos firmados (Sigstore)
- Análisis de secretos en CI; falla las compilaciones si se filtran credenciales
Ejemplo de pasos de CI (genérico):
osv-scanner ./ # or npm audit / pip-audit etc.
trivy fs --exit-code 1 .
gitleaks detect --no-banner --redact
14) Controles operativos
- Copias de seguridad cifradas con claves separadas; pruebas periódicas de restauración (automatizadas)
- RPO/RTO definidos; documenta y prueba los runbooks de incidentes
- Cuentas de emergencia con claves de hardware; supervisadas y rotadas
15) Valores predeterminados seguros y UX
- Rutas y funciones con denegación por defecto hasta que se habiliten explícitamente
- Feature flags: evalúalos en el servidor con delimitación por tenant; evita depender únicamente de la aplicación en el cliente
- UX de seguridad clara y humana: tiempos de espera de sesión, gestión de dispositivos, inscripción/recuperación sencilla de 2FA
Lista de comprobación final antes de salir a producción
- SSO/OIDC funcionando; MFA exigido para administradores
- RBAC/ABAC con delimitación explícita por tenant; denegación por defecto verificada
- Aislamiento de tenants mediante RLS o separación de servicio/db
- HTTPS + HSTS habilitados; CSP con nonces/hashes aplicada
- Cookies: Secure+HttpOnly+SameSite; rotación de sesión cuando cambien los privilegios
- CSRF en métodos inseguros; CORS estricto configurado
- Límites de velocidad en autenticación y endpoints sensibles; cuotas por tenant
- Cifrado en reposo + a nivel de campo cuando sea necesario; claves en KMS/HSM
- Cargas: detección MIME, límites de tamaño, AV, URLs firmadas, buckets privados
- Webhooks: verificación de firmas, idempotencia, reintentos, privilegio mínimo
- Logs de auditoría estructurados enviados; alertas de anomalías configuradas; sink con evidencia de manipulación
- Dependencias fijadas; SBOM generada; SCA/SAST + análisis de secretos sin problemas
- Copias de seguridad probadas; RPO/RTO documentados; controles de emergencia implementados