Обфускація збірки в CI

Де AfterPack стоїть у вашому пайплайні: лише продакшн, один раз зібрати і просувати скрізь, з рецептом для CI та правилами щодо сідів, артефактів, source map і недоступної хмари.

AfterPack є кроком після збірки: ваш бандлер створює JavaScript, а потім AfterPack його обфускує. Ми рекомендуємо обфускувати один раз, у продакшн-збірці, і просувати цей артефакт без змін через кожен етап. Один пункт нижче не є рекомендацією: розгорнутий source map повертає ваш оригінальний вихідний код, тож не допускайте source map у розгортання.

Dev не обфускується

Обфускація працює на результаті збірки, тож проти dev-сервера їй нічого робити. Vite dev, next dev і HMR роздають незібраний або частково зібраний код.

  • Розробка. Ваш бандлер працює як завжди. AfterPack простоює. Стек-трейси вказують на ваш справжній вихідний код, HMR миттєвий, налагодження звичайне.
  • Продакшн-збірка. AfterPack запускається після того, як бандл закрито. Плагін для фреймворку запускає його автоматично. На стеку з самим лише бандлером ви викликаєте його один раз.

Щоб зібрати продакшн без обфускації, наприклад для разової налагоджувальної збірки, задайте { build: { autorun: false } } у конфігурації або AFTERPACK_build_autorun=false у середовищі.

Зберіть один раз, просувайте артефакт

Обфускований бандл є артефактом, який ви випускаєте, тож найчистіший пайплайн збирає його один раз і переносить ті самі байти через кожен етап.

Build once, promote the identical obfuscated bundle across every gateProd buildbundler outputAfterPackrandom seedOne frozen artifactfrozen the moment it's builtsame bytesDevsandboxQAintegrationPreprodstagingProdlivepromote the identical obfuscated bundle at every gate

Альтернатива полягає в тому, щоб запускати AfterPack заново для кожного середовища, і вона коштує вам сигналу від тестів: кожен перехід створює бандл іншої форми, тож те, що перевіряв QA, не є тим, що роздає продакшн. Просування одного артефакту гарантує, що інтеграційні тести, які ви запускаєте в QA, працюють саме з тими байтами, що потрапляють у продакшн. Ваш цикл розробки в будь-якому разі залишається без обфускації.

Інтерфейс команди

$ npx afterpack@latest dist/ --preset=hard

Шлях до згенерованого JavaScript плюс кожен ключ конфігурації як прапорець. У CI варто явно називати каталог збірки: автоматично визначений каталог є непомітним способом обфускувати не те дерево. npx afterpack@latest verify [dir] повторно перевіряє збірку за квитанцією захисту, яку вона записала; саме цей крок слід запускати перед розгортанням. Повний опис інтерфейсу є в довіднику CLI.

Випадковий сід за замовчуванням, закріплення для відтворюваності

Сід робить кожен реліз структурно відмінним від попереднього, тож інструмент чи патч, зроблений під бандл минулого тижня, ламається на цьому. Новий випадковий сід для кожного релізу є типовим. Нічого налаштовувати не треба.

Сід не є секретом. Зберігайте його як будь-який інший параметр збірки, у змінній CI.

Закріплюйте його лише тоді, коли потрібен відтворюваний результат: влучання в кеш збірки, тести на еталонних знімках або придатні для аудиту побайтові перезбірки.

$ npx afterpack@latest dist/ --seed=git

--seed=git перетворюється на git rev-parse HEAD. Поза репозиторієм він повертається до нового випадкового сіду й друкує повідомлення замість того, щоб завершити збірку помилкою. Будь-який інший рядок рушій хешує; ціле число використовується як є. Закріплений сід щоразу дає ідентичний результат.

Закріплюйте сід на значенні, що все одно змінюється з кожним релізом

Сід, сталий між релізами, переносить аналіз зловмисника з одного релізу на наступний, а саме від цього ви й встановлювали AfterPack. Коли закріплений сід справді виправданий, прив'яжіть його до значення, що змінюється з кожним релізом, наприклад до SHA коміту.

Імена файлів і кешування

Плагін фреймворку дає кожному обфускованому файлу ім'я з хешем вмісту, яке слідує за його обфускованими байтами, тож одне ім'я ніколи не відповідає двом різним файлам, а браузери, CDN і сервіс-воркери ніколи не отримують застарілий файл під повторно використаним ім'ям. Ім'я виводиться з необфускованого вмісту файлу, а також із сіду, версії рушія, ваших налаштувань, ваших директив і режиму збірки.

Тож кешування залежить від сіду. Реліз, який бере новий сід, типово або з --seed=git, перейменовує кожен обфускований файл, тож браузери й сервіс-воркери завантажують кожен із них знову. Сталий сід зберігає байти та ім'я незміненого файлу між релізами, поки версія рушія й налаштування лишаються тими самими, тож довготривалі кеші зберігаються ціною, яку описує попередження вище.

З key версія хмарного рушія входить до імені. Коли збірка не може прочитати цю версію з API, вона попереджає й дає цій збірці випадкові імена: вони безпечні, але закріплений сід їх не відтворить.

CLI так само називає файли на збірках Next.js, Angular, Vite і webpack. На будь-якому іншому результаті він залишає імена, які записав ваш бандлер, і друкує повідомлення; якщо там ви покладаєтеся на довготривале кешування, використовуйте плагін свого фреймворку або закріпіть сталий сід.

Ротація за розкладом

Випадковий сід на кожен реліз уже змінює артефакт щоразу, коли ви випускаєте. Команди, які випускають рідко, часто додають поверх цього перезбірку за розкладом, щонічну або щотижневу, з того самого вихідного коду, тож розгорнутий бандл змінює форму кожного циклу, навіть коли код не змінюється.

Зловмисник, який почав розбирати збірку N, починає спочатку на збірці N+1: між двома збірками немає спільної структури, на яку можна було б перенести роботу. Найважливіше це для антибот-, античит- і стійкої до обходу CAPTCHA логіки, де в супротивника є час. Сама по собі ротація не зупиняє чітерство чи бот-трафік; клієнтській логіці все одно потрібна авторитетна перевірка на сервері.

У CI

Пайплайн такий: збірка, потім обфускація, потім артефакти не потрапляють у розгортання. З плагіном для фреймворку крок обфускації є частиною вашого звичайного build, і явний виклик AfterPack можна прибрати. На стеку з самим лише бандлером запускайте його самостійно.

# .github/workflows/release.yml (excerpt)
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 1. Build. With @afterpack/<fw> installed, obfuscation runs here automatically.
      - run: npm ci
      - run: npm run build

      # 2. Pure-bundler stacks only (no framework plugin): obfuscate explicitly.
      #    Pin --seed for Turborepo/Nx remote-cache hits; omit it for a fresh
      #    random seed per release. --diagnostics.level=all prints every diagnostic
      #    instead of rolling the info ones up to one line.
      - run: npx afterpack@latest dist/ --preset=hard --seed=git --diagnostics.level=all --protectionMap.enabled

      # 3. Keep the Protection Map as a reviewable CI artifact. It is off under
      #    CI=true unless asked for: --protectionMap.enabled above, or
      #    protectionMap: { enabled: true } in your plugin options. It is written to
      #    the gitignored .afterpack/ at the PROJECT ROOT, never into the deploy
      #    tree. It embeds your ORIGINAL SOURCE — treat the upload as
      #    source-confidential.
      - uses: actions/upload-artifact@v4
        with:
          name: protection-map
          path: .afterpack/

      # 4. Deploy only the bundle. Never the .afterpack/ directory, never a .map.
      - run: ./deploy.sh dist/

Turborepo і Nx будують ключі віддаленого кешу на хешах результату. Випадковий сід дає інший бандл при кожному запуску, тож ці кеші завжди промахуються. Передайте --seed=git, щоб отримати влучання в кеш: SHA все одно змінюється з кожним релізом, тож збірки залишаються відтворюваними в межах коміту і водночас змінюються між релізами.

Додайте Protection Map до PR, щоб рецензент міг підтвердити, в яких ділянках змінився рівень захисту. Це найшвидший спосіб помітити рефакторинг, який випадково виніс секрет за межі позначеної ділянки. Вважайте її артефактом для рецензента.

Команди `afterpack audit ./dist` не існує

Аудит, на якому працює сканер безпеки, запускається проти URL і друкує текст; локальний каталог він не приймає. Під'єднайте його до URL вашого розгорнутого staging після кроку розгортання. Див. Аудит.

Артефакти та як не допустити їх у розгортання

AfterPack записує три види локальних артефактів. Кожен із них чутливіший за бандл, який описує, і всі три за замовчуванням налаштовані безпечно.

АртефактЗа замовчуваннямКуди потрапляєПрапорець
Protection Map (protectionMap.html)Увімкнено, коли бандлер видав source map, який можна знайти, і збірка не працює в CI, інакше вмикається за бажаннямУ збірці Free завжди в каталог .afterpack/ у корені проєкту, внесений у gitignore, ніколи не поруч із збіркою, яку роздає сервер. Хмарна збірка Pro не записує локального файлу: її мапа зберігається в дашборді, а збірка після завершення виводить посилання на неї--protectionMap.enabled=false
Резервна копія ваших оригінальних файлівУвімкнено для CLI, вимкнено для плагіна фреймворку.afterpack/backups/<build dir>/ у корені проєкту для CLI; сусідній файл .backup.<hash> поруч із результатом, коли її запитано в плагіна--build.backup=false, щоб відмовитися
Сусідній source map .mapВимкнено в продакшені, у dev видається, лише якщо знайдено вхідний mapПоруч зі згенерованим JS--sourceMap.enabled=false

Дві речі відбуваються автоматично, в кожній інтеграції:

  • .afterpack/ ігнорує сам себе. AfterPack записує .gitignore усередині .afterpack/ і ніколи не редагує ваш власний. Файл .map або .backup.<hash>, записаний поруч зі збіркою, не ігнорується: самі не допускайте його в git і в розгортання.
  • Артефакт на шляху, який роздає сервер, викликає попередження. AfterPack попереджає, якщо артефакт потрапляє в каталог, який роздає сервер. Source map, який роздає сервер, означає повну деобфускацію.

Резервна копія є вашим оригінальним вихідним кодом дослівно. CLI тримає її в корені проєкту, у внесеному в gitignore .afterpack/backups/, з маніфестом, який записує, чим став кожен файл після обфускації; npx afterpack@latest restore повертає оригінали на місце поверх результату запуску. Плагін, у якого запитано build.backup, натомість пише свою копію поруч із результатом, де її підхопить розгортання.

Витік source map зводить обфускацію нанівець

Source map пов'язують обфускований результат прямо з вашим оригінальним вихідним кодом, а sourcesContent містить сам вихідний код. Ніколи не розгортайте їх.

Продакшн-збірка також видаляє source map, які записав ваш бандлер, бо вони містять ваш оригінальний вихідний код: файли .map, приховані та вбудовані карти, коментарі //# sourceMappingURL= і CSS-карти. Це роблять CLI і кожен плагін. Коли увімкнено sourceMap.enabled, карта AfterPack замінює JavaScript-карту бандлера, а CSS-карти залишаються. Карта, яку не вдається видалити, зупиняє збірку з помилкою. node_modules і будь-яку теку всередині каталогу збірки з власним package.json AfterPack не чіпає.

Файл, який рушій не може обфускувати, завершує збірку помилкою

Збірка зупиняється і називає файл. Використовуйте paths.exclude, щоб виділити файл, який має вийти незмінним; на сторінці Діагностика є коди і значення кожного з них.

Перевірка в CI

Будь-який ненульовий код виходу CLI означає, що збірка не випустила те, що ви налаштували, тож перевірка має вигляд if ! npx afterpack@latest dist/ ...; then fail; fi, а не розгалуження за конкретним кодом.

Це охоплює помилку використання (64), ліміт розміру (3), рушій, який треба оновити (6), а як 1 — порожній каталог збірки, помилку рушія або збірку Pro з key, яка не змогла дістатися до хмари AfterPack. Останній випадок завершує збірку помилкою замість повернення до слабшого результату, тож зелений запуск означає, що випущено саме той захист, який ви запитали. Збірки Free повністю локальні й ніколи не звертаються до хмари. Повний контракт і кожен код, який може видати збірка, описано на сторінці Діагностика.

Спостереження за збірками після випуску

Кожна збірка Pro Cloud потрапляє в дашборд: коли вона виконувалася, з якого коміту, скільки файлів обробила, скільки коштувала в MB і часі збірки, а також її покриття та роздування. Використовуйте це, щоб стежити за частотою ротації та порівнювати збірки з часом. Див. Збірки в дашборді.

Локальні збірки Free ніколи не доходять до AfterPack, тож там вони ніколи не з'являються.

Далі

  • Найкращі практики: що позначати, що не чіпати і як тримати CI чесним.
  • Збірки в дашборді: запис про кожну збірку з усім, що створила ця сторінка.
  • Модель загроз: чому збірка, що змінює форму з кожним релізом, є основним захистом.
  • Довідник CLI: повна таблиця прапорців, що стоїть за наведеним вище синопсисом.
  • Діагностика: контракт кодів виходу і правило зупинки з помилкою (fail closed), повністю.
  • Protection Map: як читати артефакт, який завантажив ваш CI-джоб.