Cuándo reescribir y cuándo aceptar un puente con el navegador: una escalera de tres niveles para integrar código desofuscado

Última actualización: 2026-07-30 11:01:49

La primera mitad de la ingeniería inversa —separar mecánicamente el código, identificar la familia del algoritmo y verificarlo por comparación diferencial— te permite afirmar: «entiendo qué calcula». Pero esa conclusión todavía no se puede desplegar. La lógica sigue atada a su runtime original y hay que decidir cómo integrarla: como una función pura apta para CI o como un proceso externo que alguien tendrá que vigilar. Elegir mal convierte el ahorro inicial en deuda operativa, con intereses.

La visión general en cuatro etapas resume este punto en una línea: «Etapa 4: degradar por capa de transporte». Aquí lo desarrollamos. La idea esencial es sencilla: cada descenso de nivel multiplica por diez la superficie de dependencias, los modos de fallo y el coste de despliegue; por defecto, hay que luchar por subir.

Las tres formas de integrar la lógica

Solo hay tres destinos posibles, ordenados de mejor a peor. Esta jerarquía debería quedar escrita en las convenciones del equipo, no resolverse sobre la marcha según «lo primero que funcione»:

  1. Reescritura nativa. Reimplementa la lógica en el lenguaje de destino, sin depender del runtime original y utilizando únicamente la biblioteca estándar. El requisito previo es haber identificado correctamente la familia algorítmica: cuando la fase de fingerprinting es correcta, el 90 % se toma de una implementación pública, los puntos de desviación restantes se resuelven por separado y el resultado queda como una función pura.

  2. Motor JS local con un fragmento mínimo. Hay lógica cuya depuración completa resulta demasiado costosa a corto plazo. En esos casos, conserva una porción reducida del JS original y ejecuta las pocas decenas de líneas necesarias en un Node/V8 local; nunca la página entera.

  3. Puente pasivo con el navegador. Cierto estado solo existe dentro de una página real con sesión iniciada: una firma entregada en runtime, un identificador dinámico vinculado a la sesión. Si la reconstrucción estática no puede reproducirlo, por ahora solo queda leerlo desde el navegador. Es una solución temporal: debe quedar señalada en las notas de interfaz y jamás convertirse en la opción predeterminada.

El coste real de bajar un nivel

El orden no es arbitrario. Los costes de estos tres niveles no crecen de forma lineal: cada uno supone un orden de magnitud más que el anterior.

Nivel

Superficie de dependencias

Modo de fallo

¿Apto para CI?

Reescritura nativa

Biblioteca estándar, ningún proceso externo

La salida no coincide; se localiza con un assert

Sí: es una función pura

Motor JS local

Un runtime adicional de Node; el contexto de V8 no es thread-safe y la concurrencia requiere un lock

Versión del motor o ausencia de un global del que depende el fragmento

Apenas: hay que instalar el motor

Puente pasivo con el navegador

Un Chrome real + extensión + una sesión mantenida por una persona + un canal loopback local

La página no está abierta, la sesión caducó, cambió la estructura o se cerró la pestaña

No: necesita a una persona activa

Los fallos del primer nivel los detecta una prueba unitaria; los del tercero son del tipo «hoy el usuario cerró esa pestaña». Convertir algo que podría ser una función pura en una solución de tercer nivel vincula a una persona a cada llamada. Como referencia, una vez identificada la familia algorítmica, un SDK de firmas ofuscado puede convertirse en una implementación autónoma de menos de 600 líneas que solo dependa de crypto integrado y funcione en el primer nivel. Lo que parecía requerir un puente con el navegador suele ser simplemente un algoritmo que aún no has identificado por completo.

Límites del puente pasivo: una instantánea y nada más

El puente pasivo solo se descontrola de una manera: cuando empieza a «ayudar». Renovar sesiones automáticamente, iniciar sesión por su cuenta o esperar cargas de forma automática lo desplaza de reenviador a crawler. Por eso sus límites deben ser extremadamente estrechos. Estas restricciones se aprendieron por las malas:

Una sola consulta instantánea; jamás modificar la página. Consulta una vez el estado de cookies, sesión y runtime de una página que ya esté abierta. No crees pestañas, no actualices, no navegues, no enfoques ventanas ni hagas polling mientras esperas. Si la página no existe, no existe: no la abras por el usuario.

Devuelve un error explícito en cuanto falte algo. Si no hay una pestaña coincidente, devuelve tab_unavailable; si la página está abierta pero no hay sesión, not_logged_in; si hay sesión pero el runtime no está listo, runtime_unavailable. Cada uno de esos tres códigos corresponde a un estado real y a una acción posterior: esperar la página, iniciar sesión o cambiar de destino. Es mucho mejor que devolver un único «falló» y obligar al llamador a adivinar qué pasó.

El estado sensible no sale nunca del navegador. La extensión no solicita permisos de cookies ni webRequest. Solo consulta una pestaña coincidente que ya está abierta, completa la solicitud dentro del contexto de esa página y depura los campos antes de responder. Las cookies y el estado de firma de la página no abandonan Chrome ni un solo paso, y el canal se conecta por defecto a una dirección loopback local. El puente transporta resultados, no credenciales.

Por qué 15 scopes solo cubren unas pocas plataformas

El puente pasivo reenvía solicitudes incluidas en una lista blanca: el adaptador restringe ruta, parámetros y referer. Su granularidad es la parte menos intuitiva: hay 15 scopes para apenas unas cuantas plataformas porque los scopes se definen por «contexto de página», no por «plataforma». Un mismo TikTok tiene Creative Center, Top Ads, la plataforma para creadores, la biblioteca de influencers y Ads Manager: son cinco scopes independientes, con cinco estados de sesión y cinco runtimes de página distintos. Tener sesión en el backend publicitario no proporciona el runtime de la plataforma para creadores. Si asignas un único bloque por plataforma, el primer caso de «tengo sesión en el subsite A, pero no puedo atender el subsite B» te obligará a replantearlo. En Xiaohongshu, el sitio principal, las rutas equivalentes a la app y el marketplace para creadores también son tres scopes distintos.

La granularidad de planificación sigue la misma lógica: el lock se aplica exclusivamente a nivel de familia de plataformas. Las solicitudes de una misma familia, como la familia Douyin, se ejecutan en serie porque reutilizan la misma pestaña real; lanzarlas a la vez en un único contexto de página hace que interfieran entre sí. Familias diferentes, como Douyin y Xiaohongshu, se ejecutan en paralelo porque son dos pestañas sin relación. Dentro de una familia, añade además un intervalo mínimo entre solicitudes. Si agrupas demasiado, serializas trabajo que podría hacerse en paralelo; si separas demasiado, chocan solicitudes que comparten pestaña. La familia de plataforma es exactamente el límite natural de «comparte el mismo runtime de página».

Traspaso explícito para iniciar sesión: la única intervención humana permitida

El puente pasivo no opera la página, pero las sesiones caducan. La solución es concentrar la intervención humana en una sola acción explícita y puntual: un comando interactivo inicia un traspaso, el programa abre mediante el sistema operativo la página de negocio correspondiente, espera a que inicies sesión manualmente y a que la página esté lista, y después repite la solicitud original. Durante todo el proceso, la extensión no pulsa botones, no rellena formularios ni exporta cookies. El inicio de sesión ocurre en un navegador real y el programa retoma la solicitud cuando has terminado.

La restricción clave es que nunca debe activarse implícitamente. Un comando no interactivo —CI o una tarea programada— jamás abre un navegador: devuelve limpiamente un error de sesión y deja la decisión en la capa superior. El traspaso también debe evitar falsos positivos. Tras iniciar sesión correctamente, el runtime dispone como máximo de una breve espera adicional —20 segundos en la implementación— antes de que tenga que haber un veredicto. Si la página de negocio ya redirigió a una página de cuenta sin el contexto de interfaz objetivo, termina la etapa actual de inmediato. No confundas un «no se puede llegar ahí» determinista con «todavía está cargando» y esperes sin sentido. Un flujo que admite degradación registra esa etapa como unavailable y continúa, en lugar de hacer fallar todo el proceso.

Estados de etapa: completada, omitida a propósito o pendiente de sesión

Hay un principio común detrás de todo lo anterior: ninguna etapa puede limitarse a devolver «éxito» o «fallo». En un pipeline de orquestación, el resultado de cada etapa adopta seis formas: completed (terminada), empty (se ejecutó, pero no produjo datos), ready (preparada, a la espera de envío), skipped (omitida deliberadamente por una regla), unavailable (no disponible en este momento, normalmente porque se necesita una sesión) y blocked (no se cumple una precondición).

«Omitida deliberadamente», «necesita una sesión» y «realmente vacía» son señales completamente diferentes. Si devuelves un único resultado opaco, no puedes distinguir entre algo vacío porque debía estarlo y algo vacío porque la sesión caducó sin que nadie lo advirtiera. El pipeline deja de ser operable. Modela el estado de cada etapa como un enum finito y la capa de orquestación —un script o un modelo— podrá decidir si degrada, vuelve a autenticar o aborta. Es el mismo principio de los tres códigos de error del puente pasivo, elevado al nivel del flujo.

Cómo usar el modelo para decidir en qué nivel cae una capacidad

El modelo solo entra en escena aquí, y con un papel limitado: no purifica la lógica por ti; ayuda a decidir si conviene hacerlo y hasta qué punto. Es un juicio de arquitectura, no la ruptura de una firma. La pregunta central es una: ¿el estado del que depende puede reconstruirse estáticamente o solo existe en runtime? A partir de ahí, hay que sopesar el esfuerzo de purificación frente a la frecuencia con que cambia. Distintas etapas piden capacidades diferentes al modelo:

Etapa

Capacidad necesaria

Elección

model id

Leer el módulo completo para ver la superficie de dependencias

Contexto largo; lee el grafo de llamadas de una pasada

Kimi K3

kimi-k3

Argumentar ambos lados de la elección de nivel y cuestionar el «haz que funcione como sea»

Razonamiento sólido; defenderá invertir un día extra en purificar

Claude Opus 5

claude-opus-5

Triaje masivo de primera pasada para decenas o cientos de capacidades

Económico y con alta concurrencia

Claude Sonnet 5

claude-sonnet-5

Atribución después de una degradación

Razonamiento intermedio; explica a partir de un log de errores

GPT-5.6 Sol

gpt-5.6-sol

La segunda fila es la más importante. El error más fácil al decidir un nivel es que el modelo siga tu tono de «haz que funcione como sea» y responda «el puente con el navegador es lo más fácil». En ese caso te está reflejando, no está calculando el coste a largo plazo. Un nivel de razonamiento sólido debería replicar: «este segmento es un hash estándar con una perturbación constante; merece un día de trabajo para convertirlo en una función pura y no debería ir al puente». No me creas sin más: pruébalo. Elige 3 fragmentos de lógica, al menos 1 cuya ubicación ya sepas que es correcta como control, y envía a claude-opus-5 y gpt-5.6-sol el mismo prompt: «propón un nivel, arguméntalo y cuestiona que vaya prematuramente al puente con el navegador». Observa una sola cosa: ¿pelea por subirte un nivel o se instala cómodamente en el tercero?

El verdadero problema es el coste de cambiar

Cuatro niveles procedentes de tres proveedores implican tres SDK, tres esquemas de autenticación y tres formatos de error. No merece la pena reescribir el cliente para cambiar de modelo entre pasos. Por eso la mayoría acaba usando un único modelo para todo y, justo en el juicio de ubicación que más necesita razonamiento, utiliza un modelo que solo le da la razón.

AIReiter aplana esa capa: una clave, una interfaz compatible con OpenAI y los cuatro niveles detrás. Cambiar de modelo consiste únicamente en modificar el campo model del cuerpo de la solicitud.

# Argumentación de ubicación: el nivel de razonamiento
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<prompt de ubicación + fragmento de lógica desofuscada + lista de dependencias>"}]
  }'

# Triaje masivo de primera pasada: cambia un campo
#   "model": "claude-sonnet-5"
# Atribución de degradación:
#   "model": "gpt-5.6-sol"

¿Ya utilizas el SDK de OpenAI? Apunta base_url a https://aireiter.com/api/v1. ¿Usas el SDK de Anthropic? Llama a POST /api/v1/messages con la misma clave. En precio, los modelos Claude tienen un 30 % de descuento sobre tarifa y los modelos GPT cuestan la mitad. El coste de este flujo se concentra en dos puntos: el triaje masivo inicial de decenas o cientos de capacidades —Sonnet, con un volumen alto de llamadas— y la entrada única de contexto largo necesaria para leer un módulo entero y ver su superficie de dependencias —Kimi, con muchos tokens por llamada—. El triaje masivo usa un modelo Claude, por lo que el descuento se aplica justo en la etapa más intensa; la argumentación de ubicación en el nivel de razonamiento también usa Claude, con un 30 % de descuento. El nivel de contexto largo de Kimi K3 está disponible con la misma clave.

Conclusión

Integrar lógica obtenida por ingeniería inversa no es un problema técnico: es un problema de costes. La escalera de tres niveles —reescritura nativa > motor JS local > puente pasivo con el navegador— no puede invertirse, porque cada descenso intercambia una función pura por un proceso con dependencias externas, intervención humana y una pestaña activa. El puente pasivo no es territorio prohibido, sino un componente temporal con límites estrictos: una instantánea, nunca actividad; un error explícito en cuanto falte la página; estado sensible que no abandona el navegador; inicio de sesión únicamente mediante traspaso explícito; y estados de etapa siempre legibles. Si mantienes esas reglas, es una solución provisional fiable; si relajas una sola, se convierte en una caja negra que nadie se atreve a mantener. El modelo ayuda a decidir en qué nivel debe quedar cada capacidad y a resistir la inercia de «haz que funcione como sea». Para saber si cada reescritura es correcta, el juez es la prueba diferencial, no el modelo.