Mejores prácticas para proteger aplicaciones SaaS

Fri Nov 21 2025

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_id de 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