# Buenas prácticas de ofuscación de JavaScript

Cómo ofuscar por defecto al enviar tu JavaScript, marcar las regiones que importan con directivas, y rotar tu build para que los atacantes no puedan adelantarse.

Source: https://www.afterpack.dev/es/docs/best-practices

## La ofuscación debería ser el valor por defecto al enviar apps web

Todo lo que hay en tu JavaScript enviado es legible. Secretos, lógica de negocio, el algoritmo del que dependes: cualquiera puede abrir el bundle, y un LLM de última generación lo explicará línea por línea si se lo pides. El [escáner de seguridad](https://www.afterpack.dev/es/security-scanner) muestra exactamente qué sale de un bundle de producción real. Ejecútalo sobre el tuyo para ver qué estás regalando ya.

Ofuscar ese bundle te da propiedades concretas y verificables:

- **Un parche escrito contra tu app deja de sobrevivir a tus releases.** Un userscript, un script de Tampermonkey o una extensión que personaliza o automatiza tu app tiene que anclarse en algo estable: un nombre de función, una cadena literal, una forma reconocible. Renombrar y codificar cadenas elimina esos anclajes, y una [semilla nueva](https://www.afterpack.dev/es/docs/config#seed) reconfigura la salida en cada build. El autor escribe el parche contra un release; ya no coincide con el siguiente, y los usuarios a los que se lo enviaron se quedan con un script roto hasta que se reescribe.
- **Un buscar-y-reemplazar contra tu código no tiene nada que coincidir.** La sustitución simple de texto, la forma más barata de automatizar o alterar una app web, funciona sobre `isSubscribed` y sobre `"Upgrade to continue"`. Ninguna de las dos cadenas está en el archivo que envías.
- **Tus cadenas dejan de ser texto legible.** Los literales de cadena se codifican, así que un `grep` para una clave, un endpoint o el texto alrededor de una puerta de pago no devuelve nada, y tampoco los escáneres y scrapers que leen bundles a escala. Los números siguen legibles en `light`; desde `medium` se codifican la mayoría de los enteros.
- **La salida es un programa remodelado.** AfterPack reescribe la lógica en formas [equivalentes](https://www.afterpack.dev/es/docs/semantic-contract) que las herramientas estándar no pueden deshacer. Un lector que trabaja el artefacto hasta el final puede recuperar un programa que funciona, pero solo con un esfuerzo real, y un build nuevo hace que una herramienta escrita contra el anterior deje de encajar.
- **El coste se repite.** Cada build tiene una forma distinta, así que una herramienta o un parche escrito contra los patrones del release anterior falla en este. Los valores que alguien ya leyó del código siguen leídos.

Nada de esto es una garantía, y no sustituye a la autoridad del lado del servidor. Cualquier código de cliente puede revertirse en algún grado, porque el runtime tiene que ejecutarlo. Lo que cambia es el precio de modificarla: un parche o una herramienta escritos contra los patrones de un build fallan en el siguiente. El resto de esta página trata de dónde gastar esa protección para que cuente.

## Rota tu build, mantente por delante de los atacantes

Un único release ofuscado es un objetivo fijo al que un rompedor de captchas, una extensión de trampas o un scraper pueden adaptarse, con suficiente esfuerzo. [Una semilla nueva](https://www.afterpack.dev/es/docs/config#seed) en cada build es el valor por defecto, así que cada build tiene una forma distinta, y las herramientas escritas contra los patrones de un build fallan en el siguiente. Quien ejecuta tu código todavía puede leer los valores que usa.

Si publicas releases con poca frecuencia, esa defensa se apaga entre uno y otro. Añade un rebuild programado, independiente de los cambios de código, para que el bundle desplegado siga rotando; [Build y CI](https://www.afterpack.dev/es/docs/builds#rotate-on-a-schedule) lo conecta a un pipeline.

## Elige un preset, luego marca lo que importa

El preset por defecto es [`light`](https://www.afterpack.dev/es/docs/config#preset) ([objetivo de complejidad](https://www.afterpack.dev/es/docs/config#complexity) 5): codifica los literales de cadena, renombra los identificadores y reescribe la sintaxis, y no añade capas estructurales. Es el preset adecuado para el bundle completo de una app. Subirlo globalmente multiplica el JavaScript que cargan tus páginas y su tiempo de arranque ([coste de carga](https://www.afterpack.dev/es/docs/presets#load-cost)), así que mide tus propias páginas antes de publicar un `medium` global o superior:

```bash
npx afterpack@latest dist/ --preset=hard
```

La escala es `minify` (0), `light` (5), `medium` (7), `hard` (12), `extreme` (25); consulta [Presets](https://www.afterpack.dev/es/docs/presets). [`--complexity=<number>`](https://www.afterpack.dev/es/docs/config#complexity) sobrescribe el objetivo que trae el preset: `--preset=hard --complexity=40` es `hard` con objetivo 40. Un valor fijado así se aplica a cada token, incluso con `light`.

Luego, en [Pro](https://www.afterpack.dev/es/docs/tiers), marca las regiones donde equivocarse cuesta más, para que el tratamiento más pesado caiga solo donde compensa su coste en carga y en tamaño.

### Marcar una región con una directiva

```ts
/* @afterpack preset=hard */
function checkLicense(token: string): boolean {
  return verifySignature(token, PUBLIC_KEY);
}
/* @afterpack end */

const apiKey = /* @afterpack preset=extreme */ "YOUR_SECRET";
// only the rest of this line is covered
```

[Directivas](https://www.afterpack.dev/es/docs/directives) es la referencia: la gramática, cada clave que una directiva puede fijar, [qué marcar y por qué](https://www.afterpack.dev/es/docs/directives#what-to-mark), y qué le pasa a una directiva en un build que no puede aplicarla. Las sobrescrituras por región tienen efecto en builds [Pro](https://www.afterpack.dev/es/docs/tiers).

## Las dos transformaciones anti-manipulación

Anti-manipulación no forma parte de `preset=hard`. Son dos flags de configuración Pro explícitas, ambas desactivadas por defecto, y no se aplican en Free:

- **[`transforms.comparisonHardening.enabled`](https://www.afterpack.dev/es/docs/config#transforms-kind-enabled)** reescribe una igualdad estricta contra un literal de cadena en tiempo de build (`x === "LIT"`) como una comprobación de resumen de un solo sentido, así que el literal está ausente del artefacto por completo. Úsalo para cualquier comprobación que compare contra una constante que no quieres que se pueda extraer con grep.
- **[`transforms.selfIntegrity.enabled`](https://www.afterpack.dev/es/docs/config#transforms-kind-enabled)** añade una comprobación sobre su propia tabla de comprobación. Sin cambios, no afecta al comportamiento. Solo detecta un cambio en esa tabla; un cambio en otra parte de tu código no se detecta. Necesita código en `medium` o superior al que adjuntarse, así que un build `light` solo lo lleva dentro de una región elevada a ese nivel. Desde el motor 0.2.3, un build sin ese código falla con [`DIAG_SELF_INTEGRITY_INERT`](https://www.afterpack.dev/es/docs/diagnostics#complete-reference) en lugar de publicarse sin la comprobación.

Actívalas ambas para todo el programa. Consulta [Configuración](https://www.afterpack.dev/es/docs/config#transforms-kind-enabled).

## Protege una licencia o prueba ligada a un dominio

AfterPack no tiene ninguna transformación integrada de bloqueo por dominio o de caducidad. Escribe la comprobación tú mismo, y AfterPack protege el JavaScript que escribas. Para una comprobación de licencia por dominio con caducidad de prueba:

```js
/* @afterpack preset=hard */
if (location.hostname !== "customer-domain.example.com") return;
if (Date.now() > TRIAL_EXPIRES_AT) return;
/* @afterpack end */
```

Con `transforms.comparisonHardening.enabled` activado, la comparación de `location.hostname` se convierte en una comprobación de resumen, así que el host permitido nunca aparece en el artefacto. Un `grep` para el dominio no encuentra nada. [`preset=hard`](https://www.afterpack.dev/es/docs/config#preset) pone la lógica circundante en el objetivo de complejidad 12 con el suelo de constantes activado, y una semilla nueva la reconfigura en cada release.

> **Note**
>
> Esto eleva el coste y el ritmo de parchear la comprobación; no elimina la comprobación de la superficie de ataque. Un atacante decidido que ejecuta el código todavía puede encontrar y parchear la comparación, pero tiene que rehacer ese trabajo en cada release. Trata esto como fricción para la prueba, y combínalo con una llamada de derecho de acceso del lado del servidor para cualquier comprobación que condicione ingresos reales.

## No gastes protección en código de terceros

Los bundles de proveedores, como React, Lodash o un runtime CSS-in-JS, valen poca protección. Ya son públicos, inflan tu bundle, y suelen usar reflexión en tiempo de ejecución que hay que reconocer.

> **`paths.exclude` es la exclusión a nivel de archivo**
>
> [`paths.exclude`](https://www.afterpack.dev/es/docs/config#paths-exclude) salta un archivo que coincide por completo, antes de analizarlo, y lo devuelve byte a byte. Un archivo excluido se reporta como `DIAG_PATH_EXCLUDED`. Funciona a nivel de archivo: cuando un build de librería concatena tu código y una dependencia en un único archivo de salida, no hay un archivo separado que excluir. Usa [`identifiers.reserved`](https://www.afterpack.dev/es/docs/config#identifiers-reserved) en su lugar.

Cuatro formas de elegir qué ve AfterPack:

- **Excluye el archivo** con [`paths.exclude`](https://www.afterpack.dev/es/docs/config#paths-exclude), por ejemplo `["**/*.min.js", "dist/vendor/**"]`. `*` y `?` coinciden dentro de un segmento de ruta, un segmento `**` completo coincide con cero o más segmentos, y la coincidencia distingue mayúsculas de minúsculas.
- **Apunta la CLI al directorio que quieres proteger.** Recorre el directorio de build recursivamente y toma cada `.js`/`.mjs`/`.cjs` que encuentra, saltando los `node_modules/` anidados (inclúyelos con [`paths.include`](https://www.afterpack.dev/es/docs/config#paths-include)) y sus propias copias `.backup.<hash>`. Un chunk de proveedor fuera de ese directorio nunca se toca.
- **Divide el código de proveedores en su propio chunk** en tu bundler, y luego ejecuta AfterPack contra un directorio que lo excluya. Un chunk de proveedor se ofusca sin problemas si prefieres aceptar el coste.
- **Marca regiones como [`skip`](https://www.afterpack.dev/es/docs/config#skip)** donde el código de una dependencia empaquetada está en el mismo archivo que el tuyo y necesita seguir siendo reconocible.

Tus imports siguen resolviéndose.

AfterPack admite por completo los campos y métodos privados de clase de ES2022: `#field` y `#method()` se analizan correctamente y se renombran y protegen como todo lo demás.

## Preserva lo que debe seguir siendo legible

Parte del código debe mantener sus nombres originales para que el runtime funcione. [Los presets de framework](https://www.afterpack.dev/es/docs/config#reflection-allow) cubren los casos comunes. Para el resto, preserva de forma acotada. Nunca desactives el renombrado globalmente, o tirarías la capa de protección más barata en todo tu bundle.

| Situación | Qué hacer |
| --- | --- |
| Exports públicos que los llamadores importan por nombre | `/* @afterpack skip */ … /* @afterpack end */` alrededor del export, o fija los nombres con [`identifiers.reserved`](https://www.afterpack.dev/es/docs/config#identifiers-reserved) |
| Stack traces que realmente depuras | Simboliza desde source maps que mantienes fuera del árbol de despliegue. [`identifiers.rename=false`](https://www.afterpack.dev/es/docs/config#identifiers-rename) mantiene cada identificador tal cual en todo el bundle |
| DI basada en reflexión ([Angular](https://www.afterpack.dev/es/docs/frameworks/angular), NestJS, Inversify) | [`reflection.allow`](https://www.afterpack.dev/es/docs/config#reflection-allow) en la configuración para reconocer un patrón personalizado; un patrón no reconocido es un error de build |
| Conmutación por `instanceof` / nombre de clase | `identifiers.reserved` con los nombres de las clases, o [`/* @afterpack skip */`](https://www.afterpack.dev/es/docs/config#skip) alrededor de la declaración |
| Símbolos de API de terceros empaquetados en el mismo archivo que tu propio código (por ejemplo, un build de librería con Rollup/Vite que inlinea una dependencia) | [`identifiers.reserved`](https://www.afterpack.dev/es/docs/config#identifiers-reserved), con un nombre plano para todo el build o una entrada `{ glob, names }` para los archivos que coinciden |
| Un script en el que nada más entra | [`identifiers.globals.rename: true`](https://www.afterpack.dev/es/docs/config#identifiers-globals-rename). Activa el renombrado de la superficie global ambiental que el motor preserva por defecto. Desactivado por defecto, y solo seguro cuando nada externo lee esos nombres |

`identifiers.reserved` preserva solo los nombres que enumeras, dondequiera que aparezcan, comparados contra el nombre a nivel de código fuente antes de cualquier renombrado: exacto, de identificador completo, sensible a mayúsculas. Disponible en Free. Recurre a él cuando no puedas marcar una región entera porque tu código y una dependencia empaquetada comparten un archivo.

Para mantener nombres en un solo lugar, usa `identifiers.reserved` o [`/* @afterpack skip */`](https://www.afterpack.dev/es/docs/config#skip); consulta [Qué puedes fijar](https://www.afterpack.dev/es/docs/directives#what-you-can-set).

Cada preservación reduce la protección en esas líneas. Aplícala solo donde el runtime lo exija.

## Estrategia de semilla

[La semilla](https://www.afterpack.dev/es/docs/config#seed) determina la forma de la salida ofuscada. Una semilla aleatoria por build es el valor por defecto. Mantenlo así en producción: un build que cambia en cada release es la defensa principal.

Fija la semilla en exactamente un lugar: en CI, cuando necesitas una salida reproducible para aciertos de caché o pruebas de snapshot dorado.

```bash
npx afterpack@latest dist/ --seed=git
```

[`--seed=git`](https://www.afterpack.dev/es/docs/config#seed) deriva la semilla de `git rev-parse HEAD`. Sin repositorio, recurre a una semilla aleatoria nueva e imprime un aviso en vez de fallar el build. Eso mantiene la salida determinista por commit y a la vez sigue rotando en cada release.

La semilla también tiene una variable de entorno, [`AFTERPACK_seed`](https://www.afterpack.dev/es/docs/config#seed), con los mismos valores que `--seed`. Los builds multi-bundle comparten una semilla automáticamente, dentro de un proceso y entre procesos, a través de esta variable. Fíjala tú mismo solo para reproducir un build concreto; un `--seed` u opción `seed` explícito sigue ganándole.

Construye producción una vez y promueve los mismos bytes a través de qa → preprod → prod. Los builds de desarrollo no están ofuscados, así que el desarrollo del día a día no se ve afectado.

No envíes una semilla constante a producción. Eso detiene el cambio de forma de la salida entre releases.

## Mantén los artefactos fuera del despliegue

AfterPack escribe tres tipos de artefacto local. Cada uno es más sensible que el bundle que describe.

- **El Protection Map** (`protectionMap.html`) incorpora tu código fuente original. Cuando está activado (por defecto, solo si el build emitió un source map y no se ejecuta en CI, ver [Protection Map](https://www.afterpack.dev/es/docs/protection-map)), se escribe en un directorio [`.afterpack/`](https://www.afterpack.dev/es/docs/cli#files) ignorado por git en la raíz del proyecto, nunca junto al build servido, y un [build de producción](https://www.afterpack.dev/es/docs/config#build-mode) avisa de que está activado. Desactívalo con [`--protectionMap.enabled=false`](https://www.afterpack.dev/es/docs/config#protectionMap-enabled).
- **La copia de seguridad de tus originales.** Antes de que la CLI reescriba un archivo en su sitio, copia el original a `.afterpack/backups/`, junto con un manifiesto de a qué se ofuscó cada archivo. Ese directorio es tu código fuente tal cual, se queda en la máquina de build, y `npx afterpack@latest restore` devuelve los originales a su lugar. Desactívalo con [`--build.backup=false`](https://www.afterpack.dev/es/docs/config#build-backup). Un plugin de framework no escribe copia por defecto; [`build.backup`](https://www.afterpack.dev/es/docs/config#build-backup) fijado a `true` ahí escribe un `.backup.<hash>` hermano junto a la salida en su lugar, donde se enviaría con el build.
- **Los source maps** encadenan la salida ofuscada directamente de vuelta al código fuente original. El [`.map` hermano](https://www.afterpack.dev/es/docs/config#sourceMap-enabled) está desactivado por defecto en producción, y el [comentario `//# sourceMappingURL=`](https://www.afterpack.dev/es/docs/config#sourceMap-emitUrl) también está desactivado por defecto en producción.

Los plugins y la CLI mantienen `.afterpack/` fuera de git con un `.gitignore` dentro de él, nunca editan el tuyo, y avisan cuando un artefacto cae bajo una ruta servida. Te corresponde a ti mantener fuera de git un archivo `.map` o `.backup.<hash>` que quede junto al build.

> **Nunca despliegues un source map ni una copia de seguridad**
>
> Un source map o una copia de seguridad contiene tu código fuente completo. Nunca despliegues ninguno de los dos.

Un build de producción también elimina los source maps que escribió tu bundler: archivos `.map`, mapas ocultos e inline, comentarios `sourceMappingURL` y mapas de CSS. Lo hacen la CLI y todos los plugins, y un mapa que no se puede eliminar hace fallar el build.

## Depura trazas de producción sin publicar el map

Un build de producción no escribe ningún `.map` ni comentario `//# sourceMappingURL=`, que es el valor por defecto correcto y te deja simbolicando stack traces a mano. Si tu equipo necesita trazas reales de producción, construye el map y mantenlo privado en vez de omitirlo.

Activa el map para el build y mantén el puntero desactivado, así nada en el JavaScript enviado anuncia que existe un map:

```json
{
  "sourceMap": { "enabled": true, "emitUrl": false, "sourcesContent": true }
}
```

Luego mantén los archivos `.map` fuera del directorio desplegado: súbelos a tu rastreador de errores o consérvalos como artefacto de CI, y simboliza ahí. Si tu despliegue copia todo el directorio de build, deniega también los archivos en el borde, para que una petición de uno nunca lo devuelva.

```nginx
location ~ \.map$ {
  return 404;
}
```

La misma regla tiene un equivalente en todas partes: una regla de CDN o WAF que coincida con `*.map`, un `deny` de proxy, o un bucket de almacenamiento que simplemente nunca reciba los archivos. Compruébalo desde fuera de tu red con `curl -I https://yoursite.example.com/assets/app.js.map`, esperando un 404, y con [`npx afterpack@latest audit`](https://www.afterpack.dev/es/docs/audit), que reporta un map alcanzable como un hallazgo.

> **Una regla de denegación es la segunda línea de defensa, no la primera**
>
> Una regla en el borde protege archivos que están en el servidor. Cualquier cosa que la sortee, una ruta mal configurada, una URL de bucket directa, un despliegue de vista previa, sirve el map con tu código fuente original dentro. No desplegar los maps es la versión que no se puede configurar mal.

## Si enviaste un secreto por accidente

Una clave de API, un token o una contraseña que llegó a tu bundle es un incidente activo. AfterPack cambia lo que un lector encuentra en el archivo enviado. No cambia el hecho de que la credencial salió.

**Lo que AfterPack le hace.** En el [preset por defecto](https://www.afterpack.dev/es/docs/presets), los literales de cadena se codifican, así que una clave guardada en uno de ellos no es legible en el archivo que despliegas. Los pocos literales que siempre quedan legibles se enumeran en [Presets](https://www.afterpack.dev/es/docs/presets#the-default-is-light). Buscar en el bundle la clave, o el texto alrededor de ella, no devuelve nada. Los escáneres automáticos de secretos y los scrapers de bundles tampoco encuentran nada, y así es como se descubren la mayoría de las claves filtradas.

**Lo que AfterPack no hace.** La credencial sigue en el build, y tu código sigue usándola. Cualquiera que ejecute tu aplicación en un navegador que controla puede alcanzar el valor en el momento en que el código lo usa. Codificarlo te compra tiempo para reaccionar. No hace que un secreto del lado del cliente sea seguro.

**Qué hacer, en orden:**

1. **Rota la clave.** Trátala como comprometida desde el momento en que salió el build. Este es el paso que cierra el incidente.
2. **Mueve la autoridad al lado del servidor.** Haz que tu propio backend guarde la credencial y decida si un llamador puede recibir el resultado. Una clave que el navegador nunca recibe no puede filtrarse.
3. **Comprueba qué está en producción.** Ejecuta [`npx afterpack@latest audit`](https://www.afterpack.dev/es/docs/audit) contra tu sitio desplegado. Reporta secretos expuestos, source maps filtrados y el nivel de protección del JavaScript que el sitio realmente sirve, así sabes qué puede extraer un lector de producción ahora mismo.
4. **Reconstruye y vuelve a desplegar**, para que el bundle que lleva el valor antiguo deje de servirse.

> **La ofuscación no sustituye a la rotación**
>
> Una credencial que se envió tiene que rotarse, sea cual sea el preset con el que se construyó.

## Prueba el build ofuscado

Ejecuta siempre tus pruebas de integración contra el build ofuscado. Probar el build previo a la ofuscación no te dice nada sobre lo que se envía.

Un fallo en el build ofuscado que pasó en el plano señala o bien una brecha de preservación (un nombre que el runtime esperaba se renombró) o un patrón de reflexión no reconocido. El diagnóstico suele nombrar la clave de configuración que lo arregla. Ejecuta con [`--diagnostics.level=all`](https://www.afterpack.dev/es/docs/config#diagnostics-level) para ver cada [diagnóstico](https://www.afterpack.dev/es/docs/diagnostics) completo en vez del resumen condensado.

> **Un build en verde es toda la señal**
>
> Cualquier cosa que hubiera enviado una protección más débil de la que configuraste detiene el build en su lugar, incluidos un archivo que el motor no puede ofuscar y un build [Pro](https://www.afterpack.dev/es/docs/tiers) que no puede alcanzar la nube. [Diagnósticos](https://www.afterpack.dev/es/docs/diagnostics#exit-codes) tiene el contrato de salida y cada código detrás de él.

## Siguiente

- [Build y CI](https://www.afterpack.dev/es/docs/builds): el ciclo de vida de promover los mismos bytes, la higiene de artefactos, y los rebuilds periódicos.
- [Modelo de amenaza](https://www.afterpack.dev/es/docs/threat-model): la escalera de adversarios contra la que se calibran estas directivas.
- [Configuración](https://www.afterpack.dev/es/docs/config): cada clave de configuración en un solo lugar.
- [Directivas](https://www.afterpack.dev/es/docs/directives): la gramática completa de marcadores de región detrás de la tabla anterior.
- [Presets](https://www.afterpack.dev/es/docs/presets): la escalera de cinco niveles que estas directivas sobrescriben por región.
- [Protection Map](https://www.afterpack.dev/es/docs/protection-map): verifica qué clase alcanzó realmente las regiones que marcaste.
- [Builds en el panel](https://www.afterpack.dev/es/docs/dashboard/builds): los mismos números, por build, para tus builds Pro Cloud.
