Loading...
Loading...
Loading...
> **Este documento define reglas NO NEGOCIABLES de seguridad y arquitectura.**
# 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
```typescript
// ❌ 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:**
```bash
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`
```typescript
// ❌ 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:**
```bash
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
```typescript
// ❌ 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
```typescript
// ❌ 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
```typescript
// ❌ 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:**
```sql
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
```typescript
// ✅ 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:**
```sql
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
```typescript
// ✅ 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ón | Límite | Ventana |
|-----------|--------|---------|
| `send_message` | 30 | 1 minuto |
| `create_job` | 10 | 1 hora |
| `purchase_lead` | 20 | 1 hora |
| `wallet_credit` | 5 | 1 minuto |
---
## 🔐 Secrets & Configuración
### SEC-001: Naming Convention para Env Vars
**Estado:** OBLIGATORIO
| Prefijo | Uso | Ejemplo |
|---------|-----|---------|
| `VITE_*` | Variables accesibles en browser | `VITE_SUPABASE_URL` |
| Sin prefijo | Server-only (Edge Functions) | `SUPABASE_SERVICE_ROLE_KEY` |
| `STRIPE_*` | Server-only Stripe | `STRIPE_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:
```bash
# 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
```bash
# 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
- [Supabase RLS Best Practices](https://supabase.com/docs/guides/auth/row-level-security)
- [Stripe Webhook Security](https://stripe.com/docs/webhooks/signatures)
- [MercadoPago Webhook Notifications](https://www.mercadopago.com.mx/developers/es/docs/your-integrations/notifications/webhooks)
- [OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
---
**Última actualización:** 2026-01-28
**Versión:** 1.0
**Aprobado por:** Senior Security Auditor
Constraints are essential. Constraints are not that hard to understand and use.
**Purpose:** Document the intentional constraints that make OpenClawfice easy to use and maintain
In [Day25](./day25-primary-key-and-entity-id.md), we discussed enumerated types (enums). To some extent, enums are also a type of constraint—they limit the values that can be assigned to a specific field to a predefined set.
The concept of a Constraint has many names: constraints, cost functions, factors, probably many others. At the most