# Найкращі практики обфускації JavaScript

Як за замовчуванням постачати обфускований JavaScript, позначати директивами важливі ділянки і постійно змінювати збірку, щоб зловмисники не могли вас випередити.

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

## Обфускація має бути стандартом для вебзастосунків, що йдуть у продакшн

Усе, що є у вашому опублікованому JavaScript, можна прочитати. Секрети, бізнес-логіку, алгоритм, на який ви покладаєтеся: будь-хто може відкрити бандл, а передова LLM на запит пояснить його рядок за рядком. [Сканер безпеки](https://www.afterpack.dev/uk/security-scanner) показує, що саме витягується з реального продакшн-бандла. Запустіть його на своєму, щоб побачити, що ви вже віддаєте.

Обфускація цього бандла дає конкретні властивості, які можна перевірити:

- **Патч, написаний під ваш застосунок, перестає переживати ваші релізи.** Юзерскрипт, скрипт для Tampermonkey чи розширення, яке налаштовує або автоматизує ваш застосунок, мусить чіплятися за щось стабільне: ім'я функції, рядковий літерал, впізнавану форму. Перейменування і кодування рядків прибирають ці якорі, а [новий сід](https://www.afterpack.dev/uk/docs/config#seed) змінює форму результату в кожній збірці. Автор пише патч під один реліз; з наступним він уже не збігається, і користувачі, яким автор його роздав, лишаються зі зламаним скриптом, доки його не перепишуть.
- **Пошуку із заміною у вашому коді нема за що зачепитися.** Проста текстова заміна, найдешевший спосіб автоматизувати чи змінити вебзастосунок, працює з `isSubscribed` і з `"Upgrade to continue"`. Жодного з цих рядків немає у файлі, який ви постачаєте.
- **Ваші рядки перестають бути читабельним текстом.** Рядкові літерали закодовано, тож `grep` ключа, ендпоінта чи тексту навколо перевірки доступу нічого не повертає, як і сканери та скрейпери, що масово читають бандли. На `light` числа лишаються читабельними; від `medium` і вище закодовано більшість цілих чисел.
- **Результат — перебудована програма.** AfterPack переписує логіку в [еквівалентні](https://www.afterpack.dev/uk/docs/semantic-contract) форми, які стандартні інструменти не можуть згорнути назад. Той, хто розбере артефакт, може відновити робочу програму, але лише ціною реальних зусиль, а після нової збірки інструмент, написаний під попередню, уже не підходить.
- **Ціна повторюється.** Кожна збірка має іншу форму, тож інструмент чи патч, написаний під шаблони попереднього релізу, ламається на цьому. Значення, які хтось уже прочитав із коду, лишаються прочитаними.

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

## Змінюйте збірку, щоб випереджати зловмисників

Один обфускований реліз — це нерухома ціль, до якої обхідник капчі, розширення-чит чи скрейпер можуть пристосуватися, якщо докласти достатньо зусиль. [Новий сід](https://www.afterpack.dev/uk/docs/config#seed) у кожній збірці діє за замовчуванням, тож кожна збірка має іншу форму, і інструменти, написані під шаблони однієї збірки, ламаються на наступній. Той, хто запускає ваш код, усе одно може прочитати значення, які він використовує.

Якщо ви випускаєте релізи рідко, між ними цей захист затихає. Додайте перезбирання за розкладом, незалежне від змін у коді, щоб розгорнутий бандл усе одно змінювався; розділ [Збірки та CI](https://www.afterpack.dev/uk/docs/builds#rotate-on-a-schedule) показує, як вбудувати це в конвеєр.

## Оберіть пресет, а потім позначте важливе

Пресет за замовчуванням — [`light`](https://www.afterpack.dev/uk/docs/config#preset) ([цільова складність](https://www.afterpack.dev/uk/docs/config#complexity) 5): він кодує рядкові літерали, перейменовує ідентифікатори та переписує синтаксис і не додає структурних шарів. Це правильний пресет для всього бандла застосунку. Глобальне підвищення в рази збільшує обсяг JavaScript, який завантажують ваші сторінки, і їхній час запуску ([ціна завантаження](https://www.afterpack.dev/uk/docs/presets#load-cost)), тож виміряйте власні сторінки, перш ніж випускати глобально `medium` або вище:

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

Шкала: `minify` (0), `light` (5), `medium` (7), `hard` (12), `extreme` (25); див. [Пресети](https://www.afterpack.dev/uk/docs/presets). [`--complexity=<number>`](https://www.afterpack.dev/uk/docs/config#complexity) перевизначає цільове значення пресету: `--preset=hard --complexity=40` — це `hard` із цільовим значенням 40. Значення, задане так, застосовується до кожного токена, навіть на `light`.

Потім на [Pro](https://www.afterpack.dev/uk/docs/tiers) позначте ділянки, де помилка коштує найдорожче, щоб важча обробка потрапила лише туди, де вона виправдовує свою ціну в завантаженні й розмірі.

### Позначте ділянку директивою

```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
```

[Директиви](https://www.afterpack.dev/uk/docs/directives) — це довідник: граматика, кожен ключ, який може задати директива, [що позначати і чому](https://www.afterpack.dev/uk/docs/directives#what-to-mark), і що відбувається з директивою у збірці, яка не може її застосувати. Перевизначення для ділянок діють у збірках [Pro](https://www.afterpack.dev/uk/docs/tiers).

## Дві трансформації захисту від втручання

Захист від втручання не входить до `preset=hard`. Це два явні прапорці конфігурації Pro, обидва за замовчуванням вимкнені й не застосовуються на Free:

- **[`transforms.comparisonHardening.enabled`](https://www.afterpack.dev/uk/docs/config#transforms-kind-enabled)** переписує строге порівняння з рядковим літералом часу збірки (`x === "LIT"`) на односторонню перевірку дайджесту, тож літерала в артефакті взагалі немає. Використовуйте його для будь-якої перевірки, що порівнює з константою, яку ви не хочете віддати `grep`.
- **[`transforms.selfIntegrity.enabled`](https://www.afterpack.dev/uk/docs/config#transforms-kind-enabled)** додає перевірку над власною таблицею перевірок. Без втручання вона не впливає на поведінку. Вона виявляє лише зміну цієї таблиці; зміну деінде у вашому коді не виявлено. Їй потрібен код рівня `medium` або вище, до якого можна прикріпитися, тож збірка на `light` містить її лише всередині ділянки, піднятої до такого рівня. Починаючи з рушія 0.2.3, збірка без такого коду завершується з [`DIAG_SELF_INTEGRITY_INERT`](https://www.afterpack.dev/uk/docs/diagnostics#complete-reference), а не виходить без перевірки.

Задавайте обидва для всієї програми. Див. [Конфігурація](https://www.afterpack.dev/uk/docs/config#transforms-kind-enabled).

## Захистіть ліцензію з прив'язкою до домену чи пробний період

AfterPack не має вбудованої трансформації прив'язки до домену чи терміну дії. Напишіть перевірку самі, а AfterPack захистить написаний вами JavaScript. Для перевірки ліцензії на домен із терміном пробного періоду:

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

Коли увімкнено `transforms.comparisonHardening.enabled`, порівняння `location.hostname` стає перевіркою дайджесту, тож дозволений хост ніколи не з'являється в артефакті. `grep` за доменом нічого не знаходить. [`preset=hard`](https://www.afterpack.dev/uk/docs/config#preset) ставить навколишню логіку на цільову складність 12 з увімкненим нижнім порогом для констант, а новий сід змінює її форму в кожному релізі.

> **Note**
>
> Це підвищує ціну й частоту патчингу перевірки; перевірку з поверхні атаки це не прибирає. Наполегливий зловмисник, який запускає код, усе ще може знайти й пропатчити порівняння, але мусить повторювати цю роботу в кожному релізі. Сприймайте це як перешкоду для зловживання пробним періодом і поєднуйте із серверною перевіркою прав доступу для будь-якої перевірки, від якої залежить реальний дохід.

## Не витрачайте захист на сторонній код

Бандли постачальників, як-от React, Lodash чи рантайм CSS-in-JS, мало що виграють від захисту. Вони й так публічні, роздувають ваш бандл і часто використовують рефлексію під час виконання, яку потрібно явно дозволити.

> **`paths.exclude` виключає на рівні файлів**
>
> [`paths.exclude`](https://www.afterpack.dev/uk/docs/config#paths-exclude) повністю пропускає відповідний файл ще до розбору і повертає його байт у байт. Про виключений файл повідомляється як `DIAG_PATH_EXCLUDED`. Це працює на рівні файлів: коли збірка бібліотеки об'єднує ваш код і залежність в один вихідний файл, окремого файлу для виключення немає. Натомість використовуйте [`identifiers.reserved`](https://www.afterpack.dev/uk/docs/config#identifiers-reserved).

Чотири способи обрати, що бачить AfterPack:

- **Виключіть файл** через [`paths.exclude`](https://www.afterpack.dev/uk/docs/config#paths-exclude), наприклад `["**/*.min.js", "dist/vendor/**"]`. `*` і `?` збігаються в межах одного сегмента шляху, цілий сегмент `**` збігається з нулем чи більше сегментів, а зіставлення чутливе до регістру.
- **Спрямуйте CLI на каталог, який хочете захистити.** Він рекурсивно обходить каталог збірки і бере кожен знайдений `.js`/`.mjs`/`.cjs`, пропускаючи вкладені `node_modules/` (включіть їх через [`paths.include`](https://www.afterpack.dev/uk/docs/config#paths-include)) і власні копії `.backup.<hash>`. Чанк постачальника поза цим каталогом ніколи не зачіпається.
- **Винесіть код постачальників в окремий чанк** у вашому бандлері, а потім запускайте AfterPack на каталозі, який його не містить. Чанк постачальника нормально обфускується, якщо ви готові прийняти ціну.
- **Позначте ділянки як [`skip`](https://www.afterpack.dev/uk/docs/config#skip)** там, де код залежності з бандла лежить в одному файлі з вашим і має залишатися впізнаваним.

Ваші імпорти й далі розв'язуються.

AfterPack повністю підтримує приватні поля й методи класів ES2022: `#field` і `#method()` коректно розбираються, перейменовуються й захищаються, як і все інше.

## Збережіть те, що має залишатися читабельним

Деякий код мусить зберігати свої оригінальні імена, щоб середовище виконання працювало. [Пресети для фреймворків](https://www.afterpack.dev/uk/docs/config#reflection-allow) покривають поширені випадки. Для решти зберігайте вибірково. Ніколи не вимикайте перейменування глобально, інакше ви втратите найдешевший шар захисту в усьому бандлі.

| Ситуація | Що робити |
| --- | --- |
| Публічні експорти, які викликачі імпортують за іменем | `/* @afterpack skip */ … /* @afterpack end */` навколо експорту або закріпіть імена через [`identifiers.reserved`](https://www.afterpack.dev/uk/docs/config#identifiers-reserved) |
| Стектрейси, які ви справді налагоджуєте | Символізуйте їх за source map, які не потрапляють у дерево розгортання. [`identifiers.rename=false`](https://www.afterpack.dev/uk/docs/config#identifiers-rename) зберігає кожен ідентифікатор без змін у всьому бандлі |
| DI на основі рефлексії ([Angular](https://www.afterpack.dev/uk/docs/frameworks/angular), NestJS, Inversify) | [`reflection.allow`](https://www.afterpack.dev/uk/docs/config#reflection-allow) у конфігурації, щоб явно дозволити власний патерн; не дозволений явно патерн є помилкою збірки |
| `instanceof` / розгалуження за іменем класу | `identifiers.reserved` з іменами класів або [`/* @afterpack skip */`](https://www.afterpack.dev/uk/docs/config#skip) навколо оголошення |
| Символи стороннього API, зібрані в один файл із вашим кодом (наприклад, бібліотечна збірка Rollup/Vite, яка вбудовує залежність) | [`identifiers.reserved`](https://www.afterpack.dev/uk/docs/config#identifiers-reserved), зі звичайним іменем для всієї збірки або записом `{ glob, names }` для відповідних файлів |
| Скрипт, у який ніщо інше не викликає | [`identifiers.globals.rename: true`](https://www.afterpack.dev/uk/docs/config#identifiers-globals-rename). Вмикає перейменування навколишньої глобальної поверхні, яку рушій інакше зберігає. За замовчуванням вимкнено, і це безпечно лише тоді, коли ніщо зовнішнє не читає ці імена |

`identifiers.reserved` зберігає лише перелічені вами імена, де б вони не траплялися, і зіставляє їх з авторським іменем на рівні вихідного коду до будь-якого перейменування: точно, за цілим ідентифікатором, з урахуванням регістру. Доступно на Free. Використовуйте його, коли не можете позначити цілу ділянку, бо ваш код і вбудована залежність лежать в одному файлі.

Щоб зберегти імена лише в одному місці, використовуйте `identifiers.reserved` або [`/* @afterpack skip */`](https://www.afterpack.dev/uk/docs/config#skip); див. [Що можна задати](https://www.afterpack.dev/uk/docs/directives#what-you-can-set).

Кожне збереження знижує захист на цих рядках. Застосовуйте його лише там, де цього вимагає середовище виконання.

## Стратегія сіду

[Сід](https://www.afterpack.dev/uk/docs/config#seed) визначає форму обфускованого результату. Випадковий сід для кожної збірки діє за замовчуванням. Залишайте так для продакшну: збірка, яка змінюється з кожним релізом, є основним захистом.

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

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

[`--seed=git`](https://www.afterpack.dev/uk/docs/config#seed) виводить сід із `git rev-parse HEAD`. Якщо репозиторію немає, він переходить на новий випадковий сід і друкує повідомлення, а не завершує збірку помилкою. Так результат детермінований для кожного коміту і водночас змінюється з кожним релізом.

Сід також має змінну середовища [`AFTERPACK_seed`](https://www.afterpack.dev/uk/docs/config#seed) з тими самими значеннями, що й `--seed`. Збірки з кількома бандлами автоматично використовують спільний сід, у межах процесу і між процесами через цю змінну. Задавайте її самі лише для відтворення конкретної збірки; явний `--seed` або параметр `seed` усе одно мають пріоритет над нею.

Зберіть продакшн один раз і просувайте ті самі байти через qa → preprod → prod. Dev-збірки не обфускуються, тож щоденна розробка не зачіпається.

Не випускайте в продакшн сталий сід. Він не дає результату змінювати форму між релізами.

## Тримайте артефакти поза розгортанням

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

- **Protection Map** (`protectionMap.html`) містить ваш оригінальний вихідний код. Коли вона увімкнена (за замовчуванням лише тоді, коли збірка видала source map і не працює в CI, див. [Protection Map](https://www.afterpack.dev/uk/docs/protection-map)), вона записується у внесений до gitignore каталог [`.afterpack/`](https://www.afterpack.dev/uk/docs/cli#files) у корені проєкту, а ніколи не поруч зі збіркою, яку роздає сервер, і [продакшн-збірка](https://www.afterpack.dev/uk/docs/config#build-mode) попереджає, що вона увімкнена. Вимкніть її через [`--protectionMap.enabled=false`](https://www.afterpack.dev/uk/docs/config#protectionMap-enabled).
- **Резервна копія ваших оригіналів.** Перш ніж переписати файл на місці, CLI копіює оригінал у `.afterpack/backups/` разом із маніфестом того, у що він обфускував кожен файл. Цей каталог — ваш вихідний код як він є, він залишається на машині збірки, а `npx afterpack@latest restore` повертає оригінали на місце. Вимкніть це через [`--build.backup=false`](https://www.afterpack.dev/uk/docs/config#build-backup). Плагін для фреймворку за замовчуванням копії не пише; якщо там задати [`build.backup`](https://www.afterpack.dev/uk/docs/config#build-backup) як `true`, натомість поруч із результатом записується сусідній файл `.backup.<hash>`, і там він потрапить у реліз разом зі збіркою.
- **Source map** пов'язують обфускований результат напряму з оригінальним вихідним кодом. [Сусідній файл `.map`](https://www.afterpack.dev/uk/docs/config#sourceMap-enabled) за замовчуванням вимкнений у продакшні, і [коментар `//# sourceMappingURL=`](https://www.afterpack.dev/uk/docs/config#sourceMap-emitUrl) за замовчуванням у продакшні теж вимкнений.

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

> **Ніколи не розгортайте source map чи резервну копію**
>
> Source map або резервна копія містить увесь ваш вихідний код. Ніколи не розгортайте ні те, ні інше.

Продакшн-збірка також видаляє source map, які записав ваш бандлер: файли `.map`, приховані та вбудовані карти, коментарі `sourceMappingURL` і CSS-карти. Це роблять CLI і кожен плагін, а карта, яку не вдається видалити, зупиняє збірку з помилкою.

## Налагоджуйте продакшн-стектрейси, не публікуючи карту

Продакшн-збірка не записує ні `.map`, ні коментаря `//# sourceMappingURL=`, і це правильна поведінка за замовчуванням, яка змушує вас символізувати стектрейси вручну. Якщо вашій команді потрібні справжні стектрейси з продакшну, збирайте карту і тримайте її приватною, замість того щоб відмовлятися від неї.

Увімкніть карту для збірки й вимкніть вказівник на неї, щоб ніщо в опублікованому JavaScript не повідомляло, що карта існує:

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

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

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

Таке саме правило має відповідник усюди: правило CDN чи WAF для `*.map`, `deny` у проксі або бакет сховища, який просто ніколи не отримує ці файли. Перевірте це ззовні вашої мережі через `curl -I https://yoursite.example.com/assets/app.js.map`, очікуючи 404, і через [`npx afterpack@latest audit`](https://www.afterpack.dev/uk/docs/audit), який повідомляє про доступну карту як про знахідку.

> **Правило заборони — друга лінія захисту, а не перша**
>
> Правило на edge захищає файли, які лежать на сервері. Усе, що його обходить, чи то неправильно налаштований маршрут, пряма URL-адреса бакета чи прев'ю-розгортання, віддає карту з вашим оригінальним вихідним кодом усередині. Не розгортати карти взагалі — це варіант, який неможливо налаштувати неправильно.

## Якщо ви випадково випустили секрет

API-ключ, токен чи пароль, що потрапив у ваш бандл, — це активний інцидент. AfterPack змінює те, що читач знаходить в опублікованому файлі. Він не змінює того факту, що облікові дані вже вийшли назовні.

**Що AfterPack із ним робить.** На [пресеті за замовчуванням](https://www.afterpack.dev/uk/docs/presets) рядкові літерали кодуються, тож ключ, що зберігається в такому літералі, не можна прочитати у файлі, який ви розгортаєте. Ті кілька літералів, що завжди залишаються читабельними, перелічено на сторінці [Пресети](https://www.afterpack.dev/uk/docs/presets#the-default-is-light). Пошук у бандлі ключа чи тексту навколо нього нічого не повертає. Автоматичні сканери секретів і скрейпери бандлів теж нічого не знаходять, а саме так виявляють більшість злитих ключів.

**Чого AfterPack не робить.** Облікові дані все ще є у збірці, і ваш код усе ще їх використовує. Будь-хто, хто запускає ваш застосунок у браузері під своїм контролем, може дістатися значення в момент, коли код його використовує. Кодування дає вам час відреагувати. Воно не робить клієнтський секрет безпечним.

**Що робити, по порядку:**

1. **Виконайте ротацію ключа.** Вважайте його скомпрометованим із моменту, коли збірка вийшла. Саме цей крок закриває інцидент.
2. **Перенесіть повноваження на сервер.** Нехай облікові дані тримає ваш власний бекенд, і він вирішує, чи може викликач отримати результат. Ключ, якого браузер ніколи не отримує, не може витекти.
3. **Перевірте, що зараз у продакшні.** Запустіть [`npx afterpack@latest audit`](https://www.afterpack.dev/uk/docs/audit) на своєму розгорнутому сайті. Він повідомляє про розкриті секрети, злиті source map і рівень захисту для JavaScript, який сайт фактично роздає, тож ви дізнаєтеся, що читач може витягти з продакшну просто зараз.
4. **Перезберіть і перерозгорніть**, щоб бандл зі старим значенням перестав роздаватися.

> **Обфускація не замінює ротацію**
>
> Облікові дані, що вийшли в реліз, потребують ротації, яким би пресетом не була зібрана збірка.

## Тестуйте обфусковану збірку

Завжди запускайте інтеграційні тести на обфускованій збірці. Тестування збірки до обфускації нічого не каже про те, що йде в реліз.

Збій на обфускованій збірці, коли звичайна проходила, вказує або на прогалину в збереженні (ім'я, яке очікувало середовище виконання, було перейменовано), або на нерозпізнаний патерн рефлексії. Діагностика зазвичай називає ключ конфігурації, який це виправляє. Запустіть із [`--diagnostics.level=all`](https://www.afterpack.dev/uk/docs/config#diagnostics-level), щоб побачити кожну [діагностику](https://www.afterpack.dev/uk/docs/diagnostics) повністю замість згорнутого підсумку.

> **Зелена збірка — це і є весь сигнал**
>
> Усе, що випустило б слабший захист, ніж ви налаштували, натомість зупиняє збірку, зокрема файл, який рушій не може обфускувати, і збірка [Pro](https://www.afterpack.dev/uk/docs/tiers), яка не може дістатися хмари. Контракт кодів виходу та кожен код за ним описано в розділі [Діагностика](https://www.afterpack.dev/uk/docs/diagnostics#exit-codes).

## Далі

- [Збірки та CI](https://www.afterpack.dev/uk/docs/builds): життєвий цикл із просуванням тих самих байтів, гігієна артефактів і періодичні перезбирання.
- [Модель загроз](https://www.afterpack.dev/uk/docs/threat-model): шкала супротивників, під яку відкалібровано ці директиви.
- [Конфігурація](https://www.afterpack.dev/uk/docs/config): кожен ключ конфігурації в одному місці.
- [Директиви](https://www.afterpack.dev/uk/docs/directives): повна граматика маркерів ділянок, що стоїть за таблицею вище.
- [Пресети](https://www.afterpack.dev/uk/docs/presets): п'ятирівнева шкала, яку ці директиви перевизначають для ділянок.
- [Protection Map](https://www.afterpack.dev/uk/docs/protection-map): перевірте, який клас насправді застосовано до позначених ділянок.
- [Збірки в дашборді](https://www.afterpack.dev/uk/docs/dashboard/builds): ті самі числа, для кожної збірки, для ваших хмарних збірок Pro.
