A Node.js permission modell stabil, a hálózatot viszont még nem korlátozza

A --permission flag 2023 vége óta stabil a Node.js-ben, és tényleg megállítja a véletlen fájlrendszer-hozzáférést. De a node:sqlite simán megkerüli, és a hálózati tiltás (--allow-net) a jelenlegi LTS-eken egyszerűen nem létezik – csak a v25/v26-ban jelent meg, Active development státusszal.

A Node.js permission modellje (a --permission flag) 2023 végén, a v22.13.0 és v23.5.0 kiadásokban lépett ki kísérleti státuszból – ma stabil API. Bekapcsolva egy Node-folyamat hozzáférését korlátozza a fájlrendszerhez, a hálózathoz, a child processekhez és néhány más erőforráshoz, mielőtt a kódod egyáltalán futni kezdene. Végigteszteltem pár konkrét esetet helyi Node 22.23.2-n, és két dolog meglepett: az egyik egy dokumentált, de kevesek által ismert kikerülési út, a másik pedig az, hogy egy „korlátozott” erőforrás a jelenlegi LTS-eken valójában egyáltalán nincs korlátozva.

Diagram: egy központi hatszögletű Node.js mag öt erőforrás-csomópontba kapcsolódik – fájlrendszer, hálózat, child process, worker threads, natív addon –, mindegyiken lakat jelzéssel. Négyen zárt zöld lakat látható (korlátozva a permission modell által), a hálózatnál nyitott narancssárga lakat (nem korlátozva).

Biztonsági öv, nem sandbox

A dokumentáció szóhasználata pontos: a permission modell egy „seat belt” – megakadályozza, hogy megbízható kód véletlenül olyan fájlhoz vagy erőforráshoz nyúljon, amihez nem kapott explicit engedélyt. Nem véd rosszindulatú kód ellen: ha a folyamat futtatja a kódot, az a Node biztonsági szabályzata szerint eleve megbízik benne, és a korlátozás megkerülhető natív addonokkal vagy más trükkökkel. Ez a keretezés fontos, mert sokan „sandboxként” gondolnak rá npm-postinstall scriptek vagy felhasználói fájlokat feldolgozó workerek elszigetelésére – valójában inkább egy hibafogó, ami egy elgépelt fs.rm-et vagy egy figyelmetlen third-party importot állít meg, nem egy ellenséges támadót.

A --permission flag nyolc erőforráskategóriát különböztet meg, mindegyikhez saját --allow-* kapcsolóval:

  • fájlrendszer olvasás/írás – --allow-fs-read / --allow-fs-write
  • hálózat – --allow-net
  • child process – --allow-child-process
  • worker thread – --allow-worker
  • natív addon – --allow-addons
  • WASI – --allow-wasi
  • FFI – --allow-ffi
  • OpenSSL STORE loader – --allow-openssl-store

Amit valóban megállít

Flag nélkül minden engedélyezett. Bekapcsolva alapból minden tilos, amíg explicit nem engedélyezed:

$ node --permission -e "require('fs').readFileSync('/etc/hostname')"

Error: Access to this API has been restricted. Use --allow-fs-read to manage permissions.
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead',
  resource: '/etc/hostname'

$ node --permission --allow-fs-read=/etc/hostname -e "console.log(require('fs').readFileSync('/etc/hostname','utf8'))"
# most már kiírja a fájl tartalmát

Ugyanígy elzárja a child_process-t és a worker_threads-et is – mindkettőt hitelesen leteszteltem, ERR_ACCESS_DENIED-del állnak meg, hacsak nem adod hozzá az --allow-child-process vagy --allow-worker flaget. Néhány kényelmi részlet is van a rendszerben: a belépési pont fájlja (és a -r-rel betöltött preload scriptek) automatikusan olvasási engedélyt kapnak, és az --allow-fs-read=/mappa automatikusan felveszi a végére a * wildcardot, ha a mappa már létezik a folyamat indulásakor – ha még nem létezik, ezt explicit ki kell írnod (/mappa/*), különben csak a pontos elérési útra kapsz jogot, a benne később létrehozott fájlokra nem.

A node:sqlite átsétál a tiltáson

A dokumentáció maga írja le, elég szárazon: a permission modell alapból a node:fs modulon keresztüli hozzáférést korlátozza, és „nem garantálja, hogy a felhasználók ne érjenek el fájlrendszert más eszközökön keresztül, mint például a node:sqlite modul”. Ez nem elméleti figyelmeztetés – helyben kipróbáltam:

$ node --permission -e "
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync('/tmp/adat.db');
db.exec('CREATE TABLE IF NOT EXISTS t (x INT)');
console.log('sikeres írás, --allow-fs-write nélkül');
"
sikeres írás, --allow-fs-write nélkül

Semmilyen --allow-fs-write flag nélkül a node:sqlite létrehozta és írta a fájlt a diszken. A node:fs-en átmenő hívásokat a permission modell natívan lefogja, de minden más útvonalat – natív addonok, más beépített modulok – neked kell számításba venned. Ha egy build scriptet vagy user-uploadot feldolgozó workert akarsz körbezárni, ne elégedj meg azzal, hogy „van rajta --permission„, nézd át, milyen modulokat importál.

A hálózati tiltás nem az, aminek hiszed

A dokumentáció ugyanúgy felsorolja a hálózatot a korlátozott erőforrások között, mint a fájlrendszert. Csakhogy a --allow-net flag hivatalos changelogja szerint ez csak a v25.0.0-ban jelent meg, és ott is „Stability: 1.1 – Active development” besorolással, ami a legalacsonyabb stabilitási szint, mielőtt valami deprecated lenne. Node 22.23.2-n (a v22 Jod LTS aktuális kiadása) a flag simán nem létezik:

$ node --permission --allow-net -e "1"
node: bad option: --allow-net

$ node --permission -e "
const http = require('node:http');
http.get('http://127.0.0.1:18234', res => console.log('kapcsolódott, status', res.statusCode));
"
kapcsolódott, status 200

Vagyis a --permission önmagában, flag-kombináció nélkül nem állítja meg a kimenő hálózati kapcsolatot a jelenlegi LTS-ágakon (v22 Jod, v24 Krypton) – a kérés simán lefut. Ez alapjaiban más, mint a fájlrendszer vagy a child process eset, ahol az alapértelmezett viselkedés a tiltás. A hálózati enforcement csak a v26 Current ágban létezik, ami a Node saját ajánlása szerint nem production-ra való (a v25-öt, ahol a flag debütált, 2026 márciusában EOL-olták, mire beérhetett volna egy LTS-be). Ha most futtatsz LTS-t és a hálózati oldalt is le akarod zárni egy folyamat körül, arra ma konténer- vagy hoszt-szintű egress szabály kell, nem a Node saját permission modellje.

Amit a legújabb kiadás már tud, az LTS még nem

Két további, kényelmesnek tűnő funkció is csak a legfrissebb release-ekben van meg. Az --permission-audit mód – ami nem dob hibát, csak egy node:diagnostics_channel csatornán jelzi, mit tiltott volna meg – a v25.8.0-ban érkezett, tehát ugyanúgy egy már EOL-olt ágban. A process.permission.drop(), amivel futásidőben, visszafordíthatatlanul lemondhatsz egy már megkapott jogosultságról (pl. a konfig beolvasása után eldobod a fájlolvasási jogot), a v26-ban jelent meg először. Helyben leteszteltem: v22.23.2-n a process.permission.drop egyszerűen undefined, TypeError-t dob, ha meghívod. Ha egy cikk vagy tutorial ezekkel demózik, érdemes megnézni, milyen Node-verzióhoz köti – LTS-en még nem indul el.

Ez is csak egy security boundary – kapott már CVE-t

A permission modell nem „állítsd be egyszer és felejtsd el” jellegű védelem, ugyanúgy hibázhat, mint bármelyik security határ. Jó példa a CVE-2025-55132: az fs.futimes() – az utimes() file-descriptoros változata – nem futtatta le a szükséges írási jogosultság-ellenőrzést, így csak olvasási joggal is meg lehetett változtatni egy fájl módosítási időbélyegét egy amúgy írásvédett mappában. A hibát a 20.20.0, 22.22.0, 24.13.0 és 25.3.0 kiadásokban javították. A tanulság nem az, hogy a permission modell haszontalan, hanem hogy ugyanolyan patch-fegyelmet igényel, mint a TLS stack vagy bármelyik más security-kritikus komponens – nem attól biztos, hogy bekapcsoltad, hanem attól, hogy naprakész Node-on fut.

Mikor van ennek gyakorlati haszna

A legkényelmesebb belépési pont egy olyan folyamat, amelynek pontosan tudod, mire van szüksége: egy feltöltött képet átméretező worker, egy build-lépés, ami csak a dist/ mappába ír, vagy egy CLI, ami CSV-t konvertál. Ezeknél reális cél --allow-fs-read=/app/src/* --allow-fs-write=/app/dist/* mellett minden mást (child process, worker, addon) alapállásban tiltani – ha egy transitive dependency supply-chain támadás áldozata lesz és shell-t próbálna spawnolni vagy máshova írni, itt akad el, mielőtt bármi történne. A hálózatot – a fentiek fényében – ne a Node permission modelljétől várd LTS-en; azt oldd meg a konténer vagy a szolgáltatás szintjén.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük