Back to .md Directory

Maistro.mx - Codebase Constraints & Guardrails

> **Este documento define reglas NO NEGOCIABLES de seguridad y arquitectura.**

May 2, 2026
0 downloads
2 views
ai guardrails
View source

Maistro.mx - Codebase Constraints & Guardrails

Este documento define reglas NO NEGOCIABLES de seguridad y arquitectura.

Cualquier PR que viole estas restricciones será rechazado automáticamente.


🔒 Seguridad (Security Constraints)

SC-001: Service Role Key NUNCA en Cliente

Estado: CRÍTICO
Violación: SUPABASE_SERVICE_ROLE_KEY expuesta en bundle cliente

// ❌ PROHIBIDO - En código cliente (src/)
const serviceRoleKey = import.meta.env.SUPABASE_SERVICE_ROLE_KEY;
const supabase = createClient(url, serviceRole_KEY); // Bypass RLS!

// ✅ CORRECTO - Solo Anon Key en cliente
const supabase = createClient(url, anonKey); // Respeta RLS

// ✅ CORRECTO - Service Role solo en Edge Functions
const supabase = createClient(url, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"));

Verificación:

npm run build
grep -r "SERVICE_ROLE" dist/ && echo "❌ FALLÓ" || echo "✅ PASS"

SC-002: Webhooks SIEMPRE Validan Firma

Estado: CRÍTICO
Violación: Aceptar webhooks sin validar x-signature o stripe-signature

// ❌ PROHIBIDO
serve(async (req) => {
  const body = await req.json(); // Sin validación!
  processPayment(body);
});

// ✅ CORRECTO - MercadoPago
const signature = req.headers.get("x-signature");
const isValid = await verifyMpSignature(signature, body, secret);
if (!isValid) return new Response("Invalid", { status: 401 });

// ✅ CORRECTO - Stripe
const event = await stripe.webhooks.constructEventAsync(body, signature, secret);

Verificación:

curl -X POST https://<project>.supabase.co/functions/v1/webhook-mercadopago \
  -d '{"type":"payment"}'
# Debe retornar 401 (sin firma)

SC-003: CORS Wildcard PROHIBIDO en Pagos

Estado: ALTO
Violación: Access-Control-Allow-Origin: * en funciones de pago

// ❌ PROHIBIDO
const corsHeaders = {
  "Access-Control-Allow-Origin": "*", // Cualquier sitio puede llamar
};

// ✅ CORRECTO
const ALLOWED_ORIGINS = [
  "https://maistro.mx",
  "https://www.maistro.mx",
  "http://localhost:5173", // Solo dev
];
const corsHeaders = {
  "Access-Control-Allow-Origin": ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0],
};

SC-004: Sanitización Obligatoria para HTML

Estado: ALTO
Violación: dangerouslySetInnerHTML sin sanitizar

// ❌ PROHIBIDO
<div dangerouslySetInnerHTML={{ __html: userContent }} />

// ✅ CORRECTO
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />

💰 Financiero (Financial Constraints)

FC-001: Actualizaciones de Wallet DEBEN ser Atómicas

Estado: CRÍTICO
Violación: Read-Modify-Write en memoria

// ❌ PROHIBIDO - Race condition
const { data: wallet } = await supabase.from("wallets").select("balance").single();
const newBalance = wallet.balance + amount; // Otro proceso puede haber cambiado esto
await supabase.from("wallets").update({ balance: newBalance });

// ✅ CORRECTO - Usar función atómica RPC
await supabase.rpc("atomic_wallet_credit", {
  p_transaction_id: transactionId,
  p_wallet_id: walletId,
  p_amount: amount,
});

// ✅ CORRECTO - SQL atómico directo
await supabase.rpc("atomic_wallet_credit", { p_wallet_id, p_amount });

Función SQL Requerida:

UPDATE maistro_wallets 
SET balance = balance + p_amount 
WHERE id = p_wallet_id;

FC-002: Idempotencia Obligatoria en Pagos

Estado: ALTO
Violación: Procesar el mismo pago/webhook múltiples veces

// ✅ CORRECTO - Verificar antes de procesar
if (await isEventProcessed(supabase, eventId)) {
  return { received: true, processed: false, reason: "already_processed" };
}

// Procesar pago...

// Marcar como procesado
await markEventProcessed(supabase, eventId, eventType);

Tabla Requerida:

CREATE TABLE maistro_processed_webhooks (
  provider VARCHAR(50),
  event_id VARCHAR(255),
  processed_at TIMESTAMPTZ,
  UNIQUE(provider, event_id)
);

🌐 API & Rate Limiting

API-001: Rate Limiting en Operaciones Sensibles

Estado: MEDIO
Aplica a: Chat, pagos, creación de jobs

// ✅ CORRECTO - Verificar rate limit antes de operar
if (!await checkRateLimit(userId, 'send_message', 30, 1)) {
  throw new Error('Rate limit exceeded: Max 30 messages per minute');
}

Límites Actuales:

OperaciónLímiteVentana
send_message301 minuto
create_job101 hora
purchase_lead201 hora
wallet_credit51 minuto

🔐 Secrets & Configuración

SEC-001: Naming Convention para Env Vars

Estado: OBLIGATORIO

PrefijoUsoEjemplo
VITE_*Variables accesibles en browserVITE_SUPABASE_URL
Sin prefijoServer-only (Edge Functions)SUPABASE_SERVICE_ROLE_KEY
STRIPE_*Server-only StripeSTRIPE_SECRET_KEY

Reglas:

  1. NUNCA usar VITE_ para secrets server-only
  2. NUNCA commit .env.local (está en .gitignore)
  3. SIEMPRE usar .env.example como template

🧪 CI/CD Guardrails

CI-001: Audit Harness Obligatorio

Estado: OBLIGATORIO

Cada deploy debe pasar:

# En pipeline CI/CD
pwsh .kimi/audit-harness.ps1 -Strict
# Exit code 0 = Deploy permitido
# Exit code != 0 = Deploy bloqueado

Checks del Harness:

  1. ✅ No secrets en código fuente
  2. ✅ TypeScript compila sin errores
  3. ✅ ESLint pasa
  4. ✅ Build exitoso
  5. ✅ No secrets en bundle de producción
  6. ✅ Dependency audit (no high severity)
  7. ✅ .env files en .gitignore
  8. ✅ HTML sanitization en dangerouslySetInnerHTML

📝 Proceso de Review

Checklist de Seguridad (Para Reviewers)

Antes de aprobar cualquier PR, verificar:

  • No hay SUPABASE_SERVICE_ROLE_KEY en archivos de src/
  • Webhooks validan firma antes de procesar
  • Funciones de pago usan CORS restricto (no wildcard)
  • dangerouslySetInnerHTML usa DOMPurify
  • Operaciones de wallet usan atomic_wallet_credit
  • Pagos/webhooks tienen idempotencia
  • Rate limiting en operaciones sensibles
  • Env vars correctamente nombradas (VITE_* vs server-only)
  • Audit harness pasa (pwsh .kimi/audit-harness.ps1)

Comandos de Verificación Rápida

# 1. Verificar no hay service role en cliente
grep -r "SERVICE_ROLE" src/ && echo "❌ FALLÓ" || echo "✅ PASS"

# 2. Verificar build no tiene secrets
npm run build
grep -r "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" dist/ && echo "❌ FALLÓ" || echo "✅ PASS"

# 3. Verificar TypeScript
npx tsc --noEmit && echo "✅ PASS" || echo "❌ FALLÓ"

# 4. Ejecutar harness completo
pwsh .kimi/audit-harness.ps1

🚨 Respuesta a Incidentes

Si se expone una SERVICE_ROLE_KEY:

  1. INMEDIATO (< 5 min):

    • Rotar key en Supabase Dashboard (Settings > API)
    • Revocar key anterior
  2. URGENTE (< 1 hora):

    • Audit logs de Supabase para accesos sospechosos
    • Verificar datos no autorizados modificados/leídos
  3. Seguimiento (< 24 horas):

    • Analizar qué datos fueron expuestos
    • Notificar usuarios afectados si aplica (GDPR/privacidad)
    • Post-mortem y mejoras al proceso

📚 Referencias


Última actualización: 2026-01-28
Versión: 1.0
Aprobado por: Senior Security Auditor

Related Documents