hardcoded_secret_123) y
API_KEY_SECRET aparece en wrangler.toml [vars], que se versiona en git.
Cualquier persona con acceso al repositorio obtiene acceso a producción.
- const enrichResp = await fetch(`https://hunter.io/api/v2/email-verifier?email=${lead.email}&api_key=hardcoded_secret_123`);
+ const enrichResp = await fetch(`https://hunter.io/api/v2/email-verifier?email=${lead.email}&api_key=${c.env.HUNTER_API_KEY}`);
- [vars] - API_KEY_SECRET = "sk-leadflow-prod-abc123" # en git
+ wrangler secret put API_KEY_SECRET + wrangler secret put HUNTER_API_KEY # Eliminar [vars] con secretos del wrangler.toml
Math.random() es determinista y predecible; no debe usarse para generar IDs de recursos (leads).
Un atacante puede predecir/enumerar IDs y acceder a datos de otros clientes.
- const leadId = Math.random().toString(36).slice(2); // ❌ predecible
+ const leadId = crypto.randomUUID(); // ✅ criptográficamente seguro
apiKey !== c.env.API_KEY_SECRET compara strings byte a byte y puede filtrar información
de temporización. Un atacante puede inferir cuántos caracteres coinciden midiendo el tiempo de respuesta.
- if (apiKey !== c.env.API_KEY_SECRET) { return c.json({ error: 'Unauthorized' }, 401); }
+ const encoder = new TextEncoder(); + const a = encoder.encode(apiKey ?? ''); + const b = encoder.encode(c.env.API_KEY_SECRET); + const match = a.byteLength === b.byteLength && + crypto.subtle.timingSafeEqual(a, b); + if (!match) return c.json({ error: 'Unauthorized' }, 401);
requestCount y recentLeads a nivel de módulo causan fugas de datos entre requests de diferentes
clientes dentro de la misma instancia V8. El array recentLeads puede crecer indefinidamente
(memory leak) y exponer leads de clientes a terceros.
- let requestCount = 0; // ❌ fuga entre requests - const recentLeads: any[] = []; // ❌ memory leak + fuga de datos
+ // Usar Workers Analytics Engine para métricas de requests + // Usar D1/KV para persistir leads — nunca módulo-global
c.env.LEADS_QUEUE.send() devuelve una Promise que no se awaita ni se pasa a
ctx.waitUntil(). El runtime puede cancelar la operación antes de que complete,
causando pérdida silenciosa de mensajes.
- c.env.LEADS_QUEUE.send({ leadId, lead, enrichData }); // ❌ floating promise
+ // Opción A: await directo (añade latencia a la respuesta) + await c.env.LEADS_QUEUE.send({ leadId, lead, enrichData }); + // Opción B: post-response con waitUntil (recomendado para no bloquear) + const ctx = c.executionCtx; + ctx.waitUntil(c.env.LEADS_QUEUE.send({ leadId, lead, enrichData }));
.text(): el body del request y la respuesta de Hunter.io. Con el límite de 128 MB del Worker,
payloads grandes agotan la memoria. Para el body del lead usar .json() directamente;
para la API de enrichment limitar con getReader() o verificar Content-Length.
- const body = await c.req.text(); - const lead = JSON.parse(body); - const enrichData = await enrichResp.text();
+ const lead = await c.req.json(); // Hono parsea directamente + const enrichData = await enrichResp.json(); // parse tipado, no texto crudo
api.cloudflare.com desde dentro del Worker añade una red hop innecesaria,
overhead de autenticación y latencia. Usar service bindings o bindings de plataforma donde sea posible.
Si la lógica es necesaria, moverla fuera del hot path (queue / workflow).
- const cfResp = await fetch(`https://api.cloudflare.com/client/v4/accounts/${lead.accountId}/workers/scripts`, { ... });
+ // Declarar en wrangler.toml: + // [[services]] + // binding = "CF_ADMIN" + // service = "leadflow-admin-worker" + const result = await c.env.CF_ADMIN.fetch(new Request('/scripts/' + lead.accountId));
Env está escrita manualmente. Si alguien añade un binding en wrangler.toml
sin actualizar la interfaz (o viceversa) se rompe la seguridad de tipos en silencio.
- interface Env { - DB: D1Database; // escrito a mano — puede divergir - LEADS_QUEUE: Queue; - }
+ $ wrangler types # genera worker-configuration.d.ts automáticamente + # En tsconfig.json: + # "include": ["src", "worker-configuration.d.ts"]
+ name = "leadflow-api" + main = "src/worker.ts" + compatibility_date = "2026-06-16" + compatibility_flags = ["nodejs_compat"] + [observability] + enabled = true + head_sampling_rate = 1 + # ✅ Sin [vars] con secretos — usar: wrangler secret put API_KEY_SECRET + # wrangler secret put HUNTER_API_KEY + [[d1_databases]] + binding = "DB" + database_name = "leadflow-prod" + database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + [[queues.producers]] + binding = "LEADS_QUEUE" + queue = "lead-processing" + [[kv_namespaces]] + binding = "SESSION_CACHE" + id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"