Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чому eval небезпечний». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`eval()` виконує рядок як звичайний JavaScript-код у поточній області видимості.** Саме тому його уникають: він виконає будь-який переданий текст, зокрема шкідливий ввід користувача (XSS та ін'єкції), працює повільно, бо рушій не може оптимізувати заздалегідь невідомий код, ламає область видимості (може створити чи перезаписати змінні навколо) і робить код непередбачуваним для читання й налагодження. Майже завжди є безпечніша заміна: `JSON.parse()` для JSON, доступ за ключем `obj[prop]` замість збирання рядка, мапа функцій замість динамічного виклику. ```javascript // bad const obj = eval('(' + jsonStr + ')'); // good const obj = JSON.parse(jsonStr); ``` **Ключове:** `eval()` перетворює дані на код, тому будь-який неконтрольований рядок стає повноцінною програмою з правами вашого застосунку.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`eval()` це вбудована функція, яка бере рядок і змушує рушій JavaScript виконати його як звичайний код.** Вона одна з найнебезпечніших і найсуперечливіших функцій мови, бо стирає межу між даними і кодом, тому її майже завжди варто замінити безпечнішою конструкцією. ## Теорія ### TL;DR - `eval(str)` виконує вміст рядка `str` як JavaScript-код і повертає значення останнього виразу. - Головна проблема це безпека: неконтрольований рядок означає виконання довільного коду, XSS та ін'єкції. - Продуктивність падає, бо рушій не може заздалегідь проаналізувати й оптимізувати такий код (JIT блокується). - `eval()` працює в поточній області видимості, тому може створювати і перезаписувати змінні навколо себе. - Для майже всіх реальних задач є заміна: `JSON.parse()`, доступ за ключем `obj[prop]`, мапа функцій, `Function()` у контрольованому випадку. ### Швидкий приклад ```javascript eval("console.log('Hello from eval!')"); // output: Hello from eval! const x = 10; const y = 5; console.log(eval('x + y')); // 15 ``` `eval` «розуміє», що рядок `'x + y'` це вираз, обчислює його в поточній області видимості і повертає результат. ### Що саме робить eval `eval()` не «парсить формулу» і не має власної пісочниці: він передає рядок тому самому рушієві, який виконує решту вашого коду, і з тими самими правами. Приклад 1, виконання виразу: ```javascript const x = 10; const y = 5; console.log(eval('x + y')); // 15 ``` Приклад 2, створення змінних (небезпечно): ```javascript eval('var z = 100;'); console.log(z); // 100 ``` Проблема в тому, що змінна `z` створюється в поточній області видимості і може перезаписати наявні значення. Приклад 3, динамічний код (погана практика): ```javascript function runCode(code) { eval(code); } runCode("alert('running arbitrary code')"); ``` Це виконає будь-який код, переданий рядком. Якщо цей рядок прийшов від користувача, ви отримуєте XSS-атаку або повний контроль над застосунком. ### Небезпека для безпеки (XSS та ін'єкції) `eval()` виконає будь-який переданий код, зокрема шкідливий, введений користувачем або зловмисником. ```javascript const userInput = "alert('pwned')"; eval(userInput); // runs straight away in the browser ``` Замість `alert` там може бути код, який читає `document.cookie`, `localStorage` чи токен сесії і надсилає їх на сторонній сервер. Це прямий шлях до XSS-атак і витоку даних. Саме тому Content Security Policy за замовчуванням блокує `eval()` директивою `unsafe-eval`. ### Зниження продуктивності Код усередині `eval()` виконується повільніше, бо рушій не може його оптимізувати: він не знає заздалегідь, що там буде. Кожен виклик `eval()` змушує рушій наново аналізувати та інтерпретувати рядок, а функція, що містить `eval()`, часто втрачає оптимізації JIT цілком, бо рушій не може довести, які змінні в ній використовуються. ### Ламає область видимості Код усередині `eval()` може створювати або змінювати змінні поточного контексту, що робить програму непередбачуваною і складною для налагодження. ```javascript let a = 10; eval('a = 99;'); console.log(a); // 99, the value changed "from the inside" ``` У строгому режимі (`'use strict'`) ситуація трохи краща: `eval()` отримує власну область видимості для оголошень, тому `var` усередині нього більше не тече назовні. Але читати й перезаписувати наявні змінні він усе одно може. ### Погана читабельність і підтримка `eval()` робить код «магічним»: до моменту виконання невідомо, що саме буде запущено. Лінтери й статичний аналіз не бачать усередині рядка нічого, автодоповнення й перейменування символів у редакторі не працюють, а стектрейси вказують на згенерований код, а не на зрозуміле місце у файлі. ### Безпечні альтернативи | Задача | Чим замінити | | --- | --- | | Виконати математичний вираз | `Function()` або готовий парсер виразів | | Розібрати JSON | `JSON.parse()` | | Виконати динамічний код із заздалегідь відомих функцій | Об'єкт з мапою функцій | | Отримати властивість за іменем | `obj[propName]`, а не `eval('obj.' + propName)` | Приклади заміни. Погано: ```javascript eval("user.name = 'Tim'"); ``` Добре: ```javascript user['name'] = 'Tim'; ``` Погано: ```javascript const obj = eval('(' + jsonStr + ')'); ``` Добре: ```javascript const obj = JSON.parse(jsonStr); ``` Погано: ```javascript eval('sum(5, 10)'); ``` Добре: ```javascript const actions = { sum: (a, b) => a + b }; actions['sum'](5, 10); ``` ### Типові помилки - Вважати, що `new Function(...)` безпечний. Він теж компілює рядок у код, просто в глобальній області видимості, тому для недовіреного вводу він так само небезпечний. - Використовувати `eval()` для розбору JSON. Це і повільніше, і вразливо; `JSON.parse()` існує саме для цього. - Будувати ім'я властивості рядком: `eval('obj.' + key)` замість `obj[key]`. Другий варіант коротший, швидший і безпечний. - Думати, що санітизація рядка робить `eval()` безпечним. Надійно екранувати довільний JavaScript практично неможливо. - Забувати про приховані форми `eval`: рядок як перший аргумент `setTimeout('doWork()', 100)` виконується так само. - Дивуватися, чому `eval()` не працює в продакшені: CSP без `unsafe-eval` блокує його, і це нормальна поведінка. ### Коротко запам'ятати | Аспект | Оцінка | | --- | --- | | Що робить | Виконує рядок як JavaScript-код | | Безпека | Дуже небезпечно: XSS та ін'єкції | | Продуктивність | Повільно | | Налагодження | Складно | | Оптимізація | Блокує JIT | | Коли використовувати | Лише в дуже рідких випадках, коли рядок повністю під вашим контролем |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.