Se sustituye eval por motivos de seguridad
La sede de la Agencia Tributaria tiene un buscador de epígrafes del IAE que un trámite puede abrir en una ventana aparte, para que se elija uno y se le devuelva. Para devolverlo, la ventana está programada para escribir una línea de programa como texto y ejecutarla. Encima lo explica un comentario: se sustituye eval por new Function por motivos de seguridad. 24 líneas más abajo, new Function está desactivado y el texto se ejecuta por un tercer camino.
- Guardado
- 31 AGO 2026
- Comprobado en vivo
- 15 SEP 2026
- Citas
- 6 en 1 fichero guardado
- Lectura
- unos 4 min

La operación tiene su intríngulis. Cuando un trámite abre el buscador, la ventana del buscador y la del trámite son dos páginas distintas, y para hablarse usan el único hilo que las une: la ventana hija sabe quién la abrió y puede llamar a una función de la madre. Qué función hay que llamar no lo sabe el buscador de antemano, porque depende del trámite, así que el nombre le llega escrito en la dirección, en un parámetro, uno de los datos que viajan al final de ella, llamado callback.

Con ese nombre y con los datos del epígrafe elegido hay que montar la llamada. Y aquí aparece el comentario, justo debajo de otro que explica el propósito de la función. Dice, con todas las letras, que se sustituye eval por new Function por motivos de seguridad, y añade entre paréntesis la razón técnica: los privilegios de eval. Es un comentario bien escrito, que dice qué se cambió y por qué, en una línea.
- Copia
- archivo
- Guardado
- 31 ago 2026
- Peso
- 103,8 kB
- sha256
- 67a5e0ae7cd7…
| 2082 | } |
| 2083 | |
| 2084 | /** |
| 2085 | * Funcion para realizar una llamada a la ventana de apertura de popup y cerrar |
| 2086 | * @param node : object (DOM) --> Objeto que realiza la llamada y que contiene la información necesaria. |
| 2087 | * @returns void. |
| 2088 | */ |
| 2089 | const closePopup = async(node) => { |
| 2090 | try{ |
| 2091 | if(callbk != null){ |
| 2092 | // Si viene de aplicación se monta el paso de variables según los parámetros solicitados |
| 2093 | // Se sustituye eval por new Function por motivos de seguridad (tiene que ver con los privilegios de eval) |
| 2094 | let code = ``; |
| 2095 | const dtipo = node.attr(`data-tipo`); |
| 2096 | const dseccion = node.attr(`data-seccion`); |
| 2097 | const epigrafe = node.attr(`data-epigrafe`); |
| 2098 | let dparams = ``; |
| 2099 | let objSelection = {}; |
| 2100 | objSelection[`tipo`] = dtipo; |
| 2101 | if(node. |
| 2102 | code = `window. |
| 2103 | objSelection[`seccion`] = dseccion; |
| 2104 | objSelection[`epigrafe`] = epigrafe; |
Traducido: eval es la orden que coge un texto y lo ejecuta como si fuera programa, y lo hace ahí mismo, dentro de la función que la llama, con acceso a todas sus variables, los datos con los que esa función está trabajando. new Function hace algo parecido desde fuera: fabrica una función con ese texto y la ejecuta sin ver las variables de quien la llama, solo las que son de toda la página. Esos son los privilegios de eval que se querían quitar. El diagnóstico es correcto, y el cambio, razonable.
El texto que se va a ejecutar se arma pegando cachos: la llamada a la función de la ventana madre, y entre comillas el tipo, la sección y el número de epígrafe, que el programa lee de los atributos de la tarjeta que el usuario acaba de pulsar. Si hay más parámetros, se añaden en un bucle, con sus comillas. Al final, un paréntesis y un punto y coma: una línea de programa escrita en el momento. El nombre que llega en la dirección no se pega en el texto: el texto dice callbk, y el nombre se busca al ejecutarlo.
- Copia
- archivo
- Guardado
- 31 ago 2026
- Peso
- 103,8 kB
- sha256
- 67a5e0ae7cd7…
| 2091 | if(callbk != null){ |
| 2092 | // Si viene de aplicación se monta el paso de variables según los parámetros solicitados |
| 2093 | // Se sustituye eval por new Function por motivos de seguridad (tiene que ver con los privilegios de eval) |
| 2094 | let code = ``; |
| 2095 | const dtipo = node.attr(`data-tipo`); |
| 2096 | const dseccion = node.attr(`data-seccion`); |
| 2097 | const epigrafe = node.attr(`data-epigrafe`); |
| 2098 | let dparams = ``; |
| 2099 | let objSelection = {}; |
| 2100 | objSelection[`tipo`] = dtipo; |
| 2101 | if(node. |
| 2102 | code = `window. |
| 2103 | objSelection[`seccion`] = dseccion; |
| 2104 | objSelection[`epigrafe`] = epigrafe; |
| 2105 | }else{ |
| 2106 | code = `window. |
| 2107 | } |
| 2108 | if(callbkParamStack && callbkParamStack.length > 0){ |
| 2109 | for(let i = 0; i < callbkParamStack.length; i++){ |
| 2110 | const param = node. |
| 2111 | dparams += `, "${param}"`; |
| 2112 | let paramName = callbk |
| 2113 | objSelection[paramName] = param; |
Y llega el momento de ejecutarla. Las dos líneas que hacen lo que anuncia el comentario, crear la función con new Function y llamarla, están ahí, una debajo de otra, desactivadas con dos barras delante, que convierten una línea de programa en un comentario. No se ejecutan. Debajo hay cuatro líneas nuevas: se crea un elemento script, la etiqueta con la que una página lleva su programa, se le mete dentro el texto, se cuelga de la cabecera del documento y se quita acto seguido, como quien entra a dejar un recado y sale sin sentarse.
- Copia
- archivo
- Guardado
- 31 ago 2026
- Peso
- 103,8 kB
- sha256
- 67a5e0ae7cd7…
| 2106 | code = `window. |
| 2107 | } |
| 2108 | if(callbkParamStack && callbkParamStack.length > 0){ |
| 2109 | for(let i = 0; i < callbkParamStack.length; i++){ |
| 2110 | const param = node. |
| 2111 | dparams += `, "${param}"`; |
| 2112 | let paramName = callbk |
| 2113 | objSelection[paramName] = param; |
| 2114 | } |
| 2115 | } |
| 2116 | code += `${dparams});`; |
| 2117 | //const _call = new Function(code); |
| 2118 | //_call(); |
| 2119 | const script = document. |
| 2120 | script.textContent = code; |
| 2121 | document. |
| 2122 | script.remove(); |
| 2123 | await send |
| 2124 | window.close(); |
| 2125 | } |
| 2126 | }catch(e){ |
| 2127 | console.log(`buscador |
| 2128 | } |
Colgar un script del documento ejecuta su contenido: es lo que hace el navegador con cada script de cada página. El texto corre, pues, como correría con eval o con new Function. Y corre desde fuera de la función, viendo solo las variables de toda la página, igual que con new Function. En lo que explica el comentario, los privilegios de eval, el tercer camino está del mismo lado que el segundo. Lo que ya no cuadra es el comentario, que anuncia un new Function que no se ejecuta. Por qué se cambió otra vez, el fichero no lo dice.
Los tres caminos comparten una cosa: la llamada se escribe primero como texto y después se ejecuta. Una vez ejecutada, esa línea hace una sola cosa, llamar a la función de la ventana madre con el tipo, la sección, el epígrafe y los parámetros. JavaScript, el lenguaje de los programas que corren en el navegador, permite también hacer esa llamada directamente, con los valores, sin texto de por medio. El comentario habla de cómo ejecutar el texto; de escribirlo no dice nada.
Falta saber de dónde salen los valores y cuándo se llega aquí. El tipo, la sección, el epígrafe y los parámetros se leen de la tarjeta que pinta el propio buscador con los datos de su búsqueda. El nombre de la función llega en la dirección. Y el botón que pone todo esto en marcha solo se pinta en las tarjetas con cuatro condiciones. La dirección tiene que traer ese nombre y no traer el identificador de convenio, que es la marca del asistente. El navegador tiene que decir de qué página se viene. Y la ventana madre tiene que tener de verdad una función con ese nombre.
- Copia
- archivo
- Guardado
- 31 ago 2026
- Peso
- 103,8 kB
- sha256
- 67a5e0ae7cd7…
| 2478 | let pvalortipopers; |
| 2479 | let pvalorresid; |
| 2480 | let pvalorestabperm; |
| 2481 | let pvaloractivprev; |
| 2482 | let pvalorrettrab; |
| 2483 | let pvalorretalq; |
| 2484 | let pvalorretmob; |
| 2485 | const pdetalle = searchParams.detalle; |
| 2486 | let fromAssistant = false; |
| 2487 | let fromApp = false; |
| 2488 | const pepigrafe = searchParams.pepigrafe; |
| 2489 | const callbk = searchParams.callback; |
| 2490 | if(callbk != null && (idConv != null && idConv != ``) && document.referrer != null && document.referrer != ``){ |
| 2491 | fromAssistant = true; |
| 2492 | }else if(callbk != null && (idConv == null || (idConv !== null && idConv == ``)) && document.referrer != null && document.referrer != ``){ |
| 2493 | fromApp = true; |
| 2494 | } |
| 2495 | let profile = local |
| 2496 | if(!fromApp && profile != null && profile != `undefined`){ |
| 2497 | setProfile(); |
| 2498 | } |
| 2499 | // Se establece la variable para saber si ha de mostrarse el modal si es una recarga de la página con perfil de persona juridica y la página se corresponde con un detalle de la sección 2. |
| 2500 | let necesaryModalActivity = false; |
| 1079 | // Cabecera de la card |
| 1080 | const header = `<li class="d-flex align-items-center mb-2 mb-lg-0 pb-2 pb-lg-0 justify-content-between border-bottom border-lg-0"> |
| 1081 | <span class="d-flex"> |
| 1082 | ${icoSection} |
| 1083 | <h3 class="mb-0 h6"> ${headerStr}</h3> |
| 1084 | </span> |
| 1085 | ${icoInfo} |
| 1086 | </li>`; |
| 1087 | |
| 1088 | let btnSelection = ``; |
| 1089 | if(callbk != null && fromApp && window.opener != null && window.opener[callbk] != null){ |
| 1090 | // Si hay callbk, existe la funcion de callbk y no viene del asistente... hay que poner un botón en la card al que hay que agregar los atributos con la información necesaria para su uso al hacer click |
| 1091 | const dataSeccion = _isIAE ? ` data-seccion=" |
| 1092 | let epiId = _source[`epigrafe`]; |
| 1093 | epiId = epiId.replace(/\./g, ``); |
| 1094 | const dataEpigrafe = _isIAE ? ` data-epigrafe="${epiId}"` : ``; |
| 1095 | let callBackParamsStr = ``; |
| 1096 | // Si existen parámetros de callback se agregan los atributos con los valores correspondientes |
| 1097 | if (callbkParamStack && callbkParamStack.length){ |
| 1098 | for (let j = 0; j < callbkParamStack.length; j++){ |
| 1099 | const param = callbkParamStack[j]; |
| 1100 | if(param == `data-epigrafe-label`){ |
Todo el bloque, además, va envuelto en un try, una red que recoge los errores: si algo falla, lo escribe en la consola del navegador con el nombre del fichero delante, y la ventana se queda abierta. Si todo va bien, después de la llamada se mandan los datos de la elección para las estadísticas del buscador y la ventana se cierra sola, con el epígrafe ya entregado al trámite que la abrió.
- Copia
- archivo
- Guardado
- 31 ago 2026
- Peso
- 103,8 kB
- sha256
- 67a5e0ae7cd7…
| 2116 | code += `${dparams});`; |
| 2117 | //const _call = new Function(code); |
| 2118 | //_call(); |
| 2119 | const script = document. |
| 2120 | script.textContent = code; |
| 2121 | document. |
| 2122 | script.remove(); |
| 2123 | await send |
| 2124 | window.close(); |
| 2125 | } |
| 2126 | }catch(e){ |
| 2127 | console.log(`buscador |
| 2128 | } |
| 2129 | } |
| 2130 | |
| 2131 | /** |
| 2132 | * Funcion para establecer si existe o no opener y una funcion pasada como parámetro controlando posible error de CORS |
| 2133 | * @param fnct : string --> Nombre de la funcion que va a comprobar su existencia. Si es null solo se comprueba existencia de opener. |
| 2134 | * @returns boolean. |
| 2135 | */ |
| 2136 | const callbackOpenerNull = (fnct) => { |
| 2137 | try{ |
| 2138 | if(fnct == null){ |
El comentario sigue explicando por qué se cambió eval por new Function. 24 líneas más abajo, new Function también está cambiado, y de eso no hay comentario.
- En el buscador de epígrafes del IAE de sede.agenciatributaria.gob.es (fichero
buscadorIAE-cards.js guardado el 31 de agosto de 2026 e idéntico el 15 de septiembre), la funciónclosePopuplleva en su línea 2093 el comentario «Se sustituye eval por new Function por motivos de seguridad (tiene que ver con los privilegios de eval)». - Las dos líneas que usarían new Function, 24 más abajo, están comentadas.
- Lo que se ejecuta en su lugar es un elemento script creado al vuelo con ese mismo texto, añadido a la cabecera del documento y retirado después; un script así se ejecuta en el ámbito global de la página, como una función creada con new Function, y no en el de la función que lo crea, como eval.
- El texto se arma con los atributos data de la tarjeta pulsada; el nombre de la función se lee del parámetro callback de la dirección y se usa como window.opener[callbk].
- El botón que llama a
closePopupsolo se pinta si hay callback, si no hay identificador de convenio, si hay página de origen y si la ventana madre tiene esa función. - El 23 de septiembre la sede sirve una versión del fichero con cambios en otras partes y estas líneas iguales.
- No decimos que esto se pueda aprovechar: los valores que se pegan dentro del texto salen de las tarjetas que pinta el propio buscador con los datos de su búsqueda, y el botón que lo pone en marcha solo aparece si la ventana que abrió el buscador tiene una función con el nombre que llega en la dirección.
- No decimos que el script sea peor que new Function en lo que explica el comentario: en eso son iguales.
- No decimos por qué se desactivó new Function: el fichero no lo explica.
- No lo hemos probado contra el servidor ni vamos a hacerlo: descargamos el fichero, que es público, y lo leímos.
- Y no decimos que el buscador funcione mal.
De dónde sale cada cita
Cada trozo de código de esta ficha está en un fichero guardado tal como lo entregó el servidor, con su huella (sha256) y su hora. Las citas entre «» se comprueban por programa contra ese fichero antes de publicar.
- https://sede.agenciatributaria.gob.es/static_files/common/script/buscadorIAE-cards.js31 de agosto de 2026, 16:29archivosha256 67a5e0ae7cd73507fa62098bf60cdbfb8722ed585a7694f471c00ad01a5e281b
- https://sede.agenciatributaria.gob.es/static_files/common/script/buscadorIAE-cards.js15 de septiembre de 2026, 08:00en vivo · byte a byte igualsha256 67a5e0ae7cd73507fa62098bf60cdbfb8722ed585a7694f471c00ad01a5e281b