Skip to content
← Todas las publicaciones
· Delta1 Labs Tutorial.NETCI/CD

Cómo ofuscar una aplicación .NET en GitHub Actions (CI/CD)

Ofusca tu aplicación .NET automáticamente en GitHub Actions: un flujo de trabajo de CI/CD paso a paso que protege tu compilación de Release en el runner, licencia solo el servidor de compilación y mantiene limpias las compilaciones de los desarrolladores.

Respuesta corta: para ofuscar una aplicación .NET en GitHub Actions, añade un paso después de dotnet publish que ejecute el ofuscador sobre tus ensamblados publicados — con Nebula.NET eso es nebula --config nebula.config.json, con la licencia suministrada desde un secreto de GitHub — y luego sube la salida protegida como tu artefacto. Ofuscar en CI (no en las máquinas de los desarrolladores) significa que tu equipo conserva compilaciones locales limpias y depurables mientras el servidor de compilación produce el único binario endurecido que distribuyes.

Por qué ofuscar en CI, no en las máquinas de los desarrolladores

.NET compila a IL, que se decompila de vuelta a C# casi original en segundos (pruébalo en ILSpy). La ofuscación reescribe ese IL para que sea difícil de leer y reconstruir. Pero dónde lo ejecutas importa:

  • En las máquinas de desarrollo: cada desarrollador necesita la herramienta y una licencia, las compilaciones locales se vuelven difíciles de depurar y es fácil distribuir una compilación sin proteger por error.
  • En CI (recomendado): la ofuscación se ejecuta una vez, en la compilación oficial de Release, en el servidor de compilación. Los desarrolladores compilan con normalidad; solo el runner está licenciado; y el artefacto que publicas siempre está protegido.

Cómo ofuscar una aplicación .NET en GitHub Actions (paso a paso)

Primero, añade un pequeño nebula.config.json a tu repositorio. Nebula se controla por completo mediante esta configuración — lista los ensamblados a proteger y dónde escribirlos (consulta la referencia de configuración completa):

{
  "inputs": ["publish/MyApp.dll"],
  "outputDirectory": "protected",
  "encryptStrings": true,
  "controlFlow": true
}

Lista todos tus propios ensamblados interdependientes juntos en inputs para que el renombrado se mantenga coherente entre ellos — consulta varios ensamblados. Las copias ofuscadas, el mapa .symbols.json y un informe se escriben en outputDirectory.

Después, el flujo de trabajo — la ofuscación va entre dotnet publish y la subida del artefacto:

name: build-protected
on:
  workflow_dispatch:
  push:
    tags: ['v*']

jobs:
  publish:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      # 1. Build the Release output as usual
      - name: Publish
        run: dotnet publish src/MyApp/MyApp.csproj -c Release -o publish

      # 2. Make the Nebula.NET CLI available on the runner. The CLI ships inside the Nebula
      #    download (the installer from delta1labs.com/download) — it is not a public nuget tool.
      #    Stage your copy on the runner and add it to PATH (host the zip yourself, e.g. behind a
      #    secret URL or as a private release asset):
      - name: Install Nebula.NET CLI
        shell: pwsh
        run: |
          Invoke-WebRequest -Uri "${{ secrets.NEBULA_CLI_URL }}" -OutFile nebula.zip
          Expand-Archive nebula.zip -DestinationPath "$env:RUNNER_TEMP/nebula"
          "$env:RUNNER_TEMP/nebula" >> $env:GITHUB_PATH

      # 3. Supply the license from a secret. NEBULA_LICENSE is a PATH to a license file;
      #    an offline signed file is ideal for CI (no online seat activation on ephemeral runners).
      - name: Write license
        shell: bash
        run: echo "${{ secrets.NEBULA_LICENSE_FILE }}" > nebula-license.json

      # 4. Obfuscate — Nebula is config-driven (there is no "obfuscate" verb)
      - name: Obfuscate
        env:
          NEBULA_LICENSE: nebula-license.json
        run: nebula --config nebula.config.json

      # 5. Ship the protected output (obfuscated assemblies + runtime sidecars)
      - uses: actions/upload-artifact@v4
        with:
          name: MyApp-protected
          path: protected

Licenciar el runner: dos formas (elige por tipo de runner)

Hay dos mecanismos de licenciamiento de Nebula, y cuál uses depende de si tu runner es efímero o persistente:

  • Runners alojados por GitHub (efímeros) — una VM nueva en cada ejecución. Usa un archivo de licencia firmado sin conexión (como en el flujo de trabajo de arriba): guarda el contenido del archivo en un secreto, escríbelo en disco en el trabajo y apunta NEBULA_LICENSE a esa ruta de archivo. No necesita activación en línea, así que nunca consumes un puesto por ejecución. NEBULA_LICENSE es una ruta de archivo, no una clave — una cadena LIC-… ahí no se resolverá.

  • Runners autoalojados / persistentes — la misma máquina cada vez. Puedes en su lugar activar tu clave en línea una sola vez:

    - name: Activate license
      run: nebula register --key ${{ secrets.NEBULA_KEY }}

    Esto usa un puesto para esa máquina y cada compilación lo reutiliza — pero no hagas esto en runners efímeros, o cada ejecución activa una “máquina nueva” y agota tus puestos.

Guarda cualquiera de los dos secretos en Settings → Secrets and variables → Actions. Consulta licenciar solo el servidor de compilación y la referencia de la CLI para todas las opciones.

Opción B: ofuscar durante la compilación (MSBuild)

Si prefieres no añadir un paso aparte, la integración de MSBuild de Nebula.NET ejecuta la ofuscación después de compilar cuando la propiedad NebulaObfuscate está establecida. Como MSBuild lee las propiedades del entorno, la condicionas a una variable que solo existe en el agente de compilación — el mismo dotnet build, sin cambios de código:

NebulaObfuscate=true
NEBULA_LICENSE=<path to your build-server license file>

Añade el paquete en tiempo de compilación de Nebula al proyecto (una dependencia solo de compilación, sin acoplamiento de código fuente ni de runtime):

<PackageReference Include="Nebula.NET.MSBuild" Version="*" PrivateAssets="all" />

Las máquinas de los desarrolladores — donde NebulaObfuscate no está establecido — compilan sin ofuscar como antes.

La integración de compilación es una función licenciada (de pago). La edición Free es solo la CLI y la aplicación de escritorio; ofuscar como parte de una compilación requiere una licencia válida en la máquina de compilación (mediante NEBULA_LICENSE, licenseFile en la configuración, o la propiedad de MSBuild NebulaLicenseFile). La CLI de la Opción A también funciona en Free, con límites en algunas funciones.

Verifica que realmente funcionó

Nunca confíes en un paso de protección que no hayas comprobado. Abre la DLL de salida de protected/ en ILSpy o dnSpy: los métodos deberían mostrar un laberinto de flujo de control while(true){ switch }, las cadenas deberían haber desaparecido y los nombres estar eliminados (excepto la API pública y cualquier cosa que necesite la reflexión). Nebula.NET verifica que cada transformación se ejecute de forma idéntica a la compilación original, pero aun así deberías ejecutar tu batería de pruebas contra el artefacto protegido antes de lanzar.

Nebula escribe un mapa de renombrado MyApp.symbols.json junto a la salida — archívalo como artefacto de CI, porque lo necesitarás para leer las trazas de pila de producción:

nebula deobfuscate --map MyApp.symbols.json --input crash.txt

Trampas que evitar

  • Reflexión / serialización / DI / XAML dependen de los nombres en tiempo de ejecución — asegúrate de que la herramienta los detecte y preserve, y excluye cualquier cosa personalizada. Esta es la causa nº 1 de “funcionaba en desarrollo, se rompió en producción”.
  • Ofusca antes de firmar el código. Si firmas primero y ofuscas después, invalidas la firma (y los hashes de anti-manipulación). Orden: publicar → ofuscar → firmar.
  • Conserva el mapa de símbolos (.symbols.json) por versión para poder desofuscar los informes de fallos más adelante.
  • Vigila el archivo único / recorte. Si publicas como archivo único o recortado, ofusca los ensamblados antes de empaquetar, y prueba esa combinación.

Próximos pasos

Consigue la edición gratuita para probar el flujo de trabajo, o lee cómo ofuscar un ensamblado .NET para los fundamentos. Para producción, Nebula.NET te da aplanado del flujo de control, cifrado de cadenas, anti-manipulación y un licenciamiento compatible con CI que no romperá tu compilación.

Preguntas frecuentes

¿Se puede ofuscar código .NET en un pipeline de CI/CD?

Sí. Ejecuta un ofuscador de .NET como un paso después de publicar la compilación de Release, de modo que solo se proteja el artefacto distribuible, y licencia solo el runner de compilación en lugar de cada máquina de desarrollador.

¿Cómo ofusco una aplicación .NET en GitHub Actions?

Publica tu aplicación, instala la CLI de Nebula.NET en el runner, ejecuta nebula --config nebula.config.json (con la licencia desde un secreto de GitHub), y luego sube la salida protegida como tu artefacto de versión.

¿Debería ofuscar en las máquinas de los desarrolladores o solo en CI?

Solo en CI. Los desarrolladores compilan binarios sin ofuscar, totalmente depurables, en local; el servidor de compilación produce el único artefacto endurecido que distribuyes — lo que mantiene limpia la depuración y limita el licenciamiento al runner.

¿La ofuscación romperá mi aplicación en el pipeline?

Puede hacerlo si algo resuelto por nombre en tiempo de ejecución (reflexión, serialización, DI, XAML) se renombra. Usa una herramienta que los detecte y preserve, ofusca antes de firmar, y ejecuta tus pruebas contra la compilación protegida.

¿Necesito una licencia de Nebula.NET en la máquina de cada desarrollador para ofuscar en CI?

No. Con la ofuscación en el servidor de compilación licencias solo el/los runner(s) de CI. Un archivo de licencia firmado sin conexión es ideal para runners efímeros — no necesita activación en línea de puesto — y apuntas NEBULA_LICENSE al archivo.

Prueba Nebula.NET

Endurece tu código .NET en minutos — empieza con la edición gratuita.