Ofusca una app de Angular

En Angular v17+ la ofuscación es una pasada posterior al build sobre dist/<app>/browser: con la CLI o con el helper @afterpack/angular.

Angular moderno (v17+) compila con @angular-devkit/build-angular:application. Ese builder se basa en esbuild pero no expone ningún hook para plugins, así que, a diferencia de los plugins de Vite, webpack y esbuild, aquí AfterPack no puede engancharse al build.

La integración es una pasada posterior al build sobre el bundle de navegador emitido. @afterpack/angular es un envoltorio de conveniencia sobre esa pasada. No es un plugin del builder.

Instalación

$ npm install -D @afterpack/angular
// scripts/obfuscate.mjs — run after `ng build`
import { afterpackAngular } from "@afterpack/angular";

await afterpackAngular({ seed: "git" });

O ejecuta directamente la CLI, sin instalar nada.

Build

El builder de aplicación escribe el bundle de cliente en dist/<app>/browser.

Con el helper, findAngularBrowserDir(distRoot) encuentra la salida de navegador. En un monorepo, pasa browserDir. findAngularBrowserDir también se exporta por separado si solo quieres el localizador.

Con la CLI, apúntala directamente a la salida de navegador:

// package.json
{
  "scripts": {
    "build": "ng build && afterpack dist/my-app/browser --seed=git"
  }
}

Apunta la CLI a ella y cada chunk .js emitido se ofusca en el sitio. La copia de seguridad de la CLI va a .afterpack/backup/ en la raíz del proyecto, fuera de dist/, así que no se publica con el despliegue; npx afterpack@latest restore deshace la ejecución. El caso al que hay que prestar atención es el helper @afterpack/angular: ahí, build: { backup: true } escribe un archivo hermano .backup.<hash> dentro de dist/, es decir, tu código fuente original dentro del árbol que despliegas.

El builder escribe el bundle antes de que pueda ejecutarse la pasada, así que tu bundle sin ofuscar está en disco hasta que la pasada termina, y cualquier cosa que pueda leer dist/ en ese intervalo puede leerlo. Una pasada fallida deja esa salida en su sitio: el build falla, así que no se publica nada, pero los archivos permanecen hasta que el siguiente build los sobrescribe.

Falla de forma segura (fail closed): afterpackAngular lanza un error si el directorio de navegador no contiene ningún .js/.mjs/.cjs, o si el motor informa de un diagnóstico de fallo.

Verificación

Activa en angular.json la opción de build sourceMap del propio Angular para el build que quieras inspeccionar, de modo que el Protection Map pueda mostrar tu código fuente original. Se escribe en .afterpack/protectionMap.html, ignorado por git. Contiene todo tu código fuente. Nunca lo subas al repositorio ni lo sirvas.

Tanto la CLI como el helper escriben el Protection Map por defecto solo cuando existen source maps y el build no se ejecuta en CI (CI=true o CI=1); el modo de producción no lo desactiva. ng build no emite source maps en su configuración de producción, así que ahí el mapa está desactivado por defecto. protectionMap: { enabled: true } fuerza su activación.

Nunca publiques source maps en tu CDN

Un source map de producción da acceso a tu código fuente original. Genéralo para el build y después déjalo fuera del dist/ que despliegas.

Opciones

Cualquier clave de configuración se puede pasar aquí en el objeto de opciones, por ejemplo afterpackAngular({ preset: "hard", complexity: 40 }). Estas opciones solo existen en este paquete, para localizar la salida del build:

OpciónTipoPor defecto
cwdstringprocess.cwd(); la raíz del proyecto a la que pertenecen .gitignore y .afterpack/
distRootstring<cwd>/dist; dónde buscar el subdirectorio browser
browserDirstringsin establecer. Omite por completo la localización automática

Aquí no existe la opción build.autorun.

Directivas

directives.enabled se rechaza aquí, en todas las capas: el objeto de opciones, AFTERPACK_directives_enabled y afterpack.json por igual. El builder de aplicación de Angular está sellado, así que esta pasada solo ve salida ya minificada y no tiene ningún hook desde el que capturar los comentarios /* @afterpack ... */. Establecerla hace fallar el build en lugar de ignorarse en silencio. Las regions escritas a mano siguen funcionando, porque nombran rangos de bytes en lugar de comentarios.

Casos límite

Angular antiguo (webpack). Los proyectos que siguen con el antiguo builder basado en webpack pueden ofuscar dentro del build con @afterpack/webpack mediante un builder personalizado. La vía posterior al build descrita arriba funciona igual con ambos builders y es la recomendada.

Siguiente