From 98f02eb02ebef5df1f3a94ddeb5173668febe2e3 Mon Sep 17 00:00:00 2001 From: Carlos Narro Date: Sun, 7 Jun 2026 19:04:27 +0200 Subject: [PATCH] Bypass de llamadas fail-closed (review de seguridad) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El gate del allowlist miraba allow.length>0 (tras parsear), así que un typo en RETELL_ALLOWED_NUMBERS que no dejara ningún número válido abría las llamadas a todos (fail-open). Ahora mira la presencia de la variable: si está puesta, se enforce; un typo restringe a nadie (fail-closed), acorde a la intención. Co-Authored-By: Claude Opus 4.8 --- mvp/b2c/src/lib/voice/retell.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mvp/b2c/src/lib/voice/retell.ts b/mvp/b2c/src/lib/voice/retell.ts index f02ee52..e1fce34 100644 --- a/mvp/b2c/src/lib/voice/retell.ts +++ b/mvp/b2c/src/lib/voice/retell.ts @@ -53,12 +53,15 @@ export async function iniciarLlamadaSaliente(opts: { // Bypass de seguridad (fase de pruebas): si RETELL_ALLOWED_NUMBERS tiene valor, SOLO se llama a // esos números; cualquier otro se omite (devuelve null, el funnel sigue en simulado). Vaciar la // variable para volver a llamar a todos. + // FAIL-CLOSED: el gate mira la PRESENCIA de la variable, no si parsea. Así un typo que no deje + // ningún número válido restringe a nadie (no llama), en vez de abrirse a todos. + const allowConfigurado = (env.RETELL_ALLOWED_NUMBERS ?? '').trim().length > 0; const allow = (env.RETELL_ALLOWED_NUMBERS ?? '') .split(',') .map((s) => normalizarTelefonoEs(s.trim())) .filter((n): n is string => Boolean(n)); - if (allow.length > 0 && !allow.includes(toNumber)) { - console.warn(`Retell: ${toNumber} no está en RETELL_ALLOWED_NUMBERS; llamada omitida (bypass).`); + if (allowConfigurado && !allow.includes(toNumber)) { + console.warn(`Retell: ${toNumber} no permitido por RETELL_ALLOWED_NUMBERS; llamada omitida (bypass).`); return null; }