Node.js izin modeli, bir sürecin hangi kaynaklara erişebileceğini başlangıçta belirlemenizi sağlar: node --permission app.js ile başlattığınızda dosya sistemi, ağ, alt süreç, worker thread, yerel eklenti, WASI, FFI ve inspector erişimi varsayılan olarak kapanır; ihtiyacınız olanı --allow-fs-read, --allow-fs-write, --allow-net, --allow-child-process gibi bayraklarla tek tek açarsınız. İzin verilmeyen bir işlem ERR_ACCESS_DENIED hatası fırlatır.

Model, YAML kaydına göre v23.5.0 ve v22.13.0 ile deneysel olmaktan çıktı ve belgelerde 2 - Stable olarak geçer. Ancak belgeler bunun bir "emniyet kemeri" olduğunu, kötü niyetli koda karşı güvenlik garantisi vermediğini açıkça yazar: Node.js çalıştırması istenen kodu güvenilir kabul eder, model yalnızca güvendiğiniz kodun yanlışlıkla izin verilmeyen bir kaynağa dokunmasını engeller. Bu yazıdaki örnekler Node.js v26.13.0 ile çalıştırıldı.

İzin modeli neyi kısıtlar?

İzin modeli neyi kısıtlar? Dosya okuma, Dosya yazma, --allow-net, Alt süreç
KapsamAçan bayrakhas() adıKararlılık
Dosya okuma--allow-fs-readfs.readStable
Dosya yazma--allow-fs-writefs.writeStable
Ağ--allow-netnet1.1 Active development
Alt süreç--allow-child-processchild1.1 Active development
Worker thread--allow-workerworker1.1 Active development
Yerel eklenti--allow-addonsaddon1.1 Active development
WASI--allow-wasiwasi1.1 Active development
Inspector--allow-inspectorinspector1.0 Early development
FFI--allow-ffiffi1.1 Active development
OpenSSL STORE--allow-openssl-storeopenssl.store1.1 Active development

Modelin kendisi kararlı olsa da bazı --allow-* bayraklarının kendi kararlılık etiketi daha düşüktür; tabloda bunları belirttik. --allow-net v25.0.0 ile, --allow-ffi ve --allow-openssl-store v26 serisinde eklendi. İzin modeli açıkken inspector için SIGUSR1 dinleyicisi de oluşturulmaz.

İlk örnek: yalnızca bir klasörü okumak

Uygulamamız veri/ayar.json dosyasını okuyacak; aynı dizinde parola içeren bir gizli klasörü de var. Betik izinleri sorguluyor, ardından izinli ve izinsiz dosyaya erişmeyi deniyor:

javascript
// uygulama.mjs
import { readFileSync, writeFileSync } from 'node:fs';

console.log('izin modeli açık mı:', process.permission !== undefined);
console.log('fs.read veri:', process.permission?.has('fs.read', './veri/ayar.json'));
console.log('fs.read gizli:', process.permission?.has('fs.read', './gizli/anahtar.txt'));
console.log('fs.write:', process.permission?.has('fs.write'));
console.log('child:', process.permission?.has('child'));

const ayar = JSON.parse(readFileSync('./veri/ayar.json', 'utf8'));
console.log('ayar okundu:', ayar);

try {
  readFileSync('./gizli/anahtar.txt', 'utf8');
} catch (err) {
  console.log(err.code, err.permission, err.resource.split(/[\\/]/).slice(-2).join('/'));
}

try {
  writeFileSync('./veri/log.txt', 'deneme');
} catch (err) {
  console.log(err.code, err.permission);
}
bash
$ node --permission --allow-fs-read=./veri uygulama.mjs
izin modeli açık mı: true
fs.read veri: true
fs.read gizli: false
fs.write: false
child: false
ayar okundu: { port: 3000 }
ERR_ACCESS_DENIED FileSystemRead gizli/anahtar.txt
ERR_ACCESS_DENIED FileSystemWrite

process.permission nesnesi yalnızca --permission ya da --permission-audit ile başlatıldığında vardır; bayraksız çalıştırdığımızda ilk satır false yazdı ve ?. sayesinde diğer sorgular undefined döndü. İzin modeli açıkken izinli klasör okunabildi, gizli klasörü FileSystemRead izniyle, yazma ise FileSystemWrite izniyle reddedildi. Hata nesnesinin permission ve resource alanları neyin engellendiğini söyler. Giriş dosyası uygulama.mjs ise ayrıca izin vermeden okunabildi: v24.2.0 ile uygulamanın giriş noktaları ve -r ile yüklenen modüller örtük okuma izni alır. Hiç --allow-fs-read vermeseydik ilk okuma yakalanmayan bir hata olarak süreci durdururdu:

bash
$ node --permission uygulama.mjs
...
Error: Access to this API has been restricted. Use --allow-fs-read to manage permissions.
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead',
  resource: '/home/kullanici/proje/veri/ayar.json'
}

Node.js v26.13.0

Windows'ta resource alanı \\?\C:\... biçiminde tam yol olarak gelir; çıktıyı kısalttık. Hata kodlarının genel yapısı için ERR_* hata kodları yazısına bakabilirsiniz.

Dosya sistemi izinlerinin kuralları

  • --allow-fs-read=* ve --allow-fs-write=* ilgili işlemlerin tamamına izin verir.
  • Göreli yollar çalışma dizinine göre çözülür, mutlak yollar da kabul edilir.
  • Birden fazla yol için bayrağı tekrarlarsınız: --allow-fs-read=/a/ --allow-fs-read=/b/. v20.7.0 ile virgülle ayırma kaldırıldı; tek bayrağa virgül yazarsanız uyarı alırsınız.
  • /home/test* gibi bir joker hem /home/test/dosya hem /home/test2 ile eşleşir; yıldızdan sonraki karakterler yok sayılır.
  • Belirttiğiniz klasör başlangıçta varsa sonuna otomatik joker eklenir. Henüz var olmayan bir klasöre izin vermek için /yol/klasor/* biçimini açıkça yazın.
  • Bir dosya tanımlayıcısı üzerinde çalışan fsync, fchmod, fchown, futimes gibi işlemler izin modeli açıkken tamamen devre dışıdır.

Klasörü yazma izniyle açtığınızda process.permission.has('fs.write') referanssız sorgu olduğu için yine false döner; bu çağrı bütün dosya sistemine yazma izni olup olmadığını sorar. Belirli bir yolu sormak için ikinci argümanı verin. v26.12.0 ile bu argüman URL ya da Uint8Array da olabilir. Dosya işlemlerinin kendisi için dosya okuma ve dosya yazma rehberlerine bakabilirsiniz.

Alt süreç, worker ve ağ

Alt süreç, worker ve ağ
javascript
// digerleri.mjs
import { execFileSync } from 'node:child_process';
import { Worker } from 'node:worker_threads';
import { readFileSync } from 'node:fs';

try {
  execFileSync(process.execPath, ['--version']);
} catch (err) {
  console.log('child:', err.code, err.permission);
}

try {
  new Worker('console.log(1)', { eval: true });
} catch (err) {
  console.log('worker:', err.code, err.permission);
}

try {
  await fetch('http://127.0.0.1:3999/');
} catch (err) {
  console.log('net:', err.cause?.code ?? err.code, err.cause?.message ?? err.message);
}

// Başlangıçta oku, sonra izni bırak
const ayar = readFileSync('./veri/ayar.json', 'utf8');
process.permission.drop('fs.read', './veri');
console.log('drop sonrası:', process.permission.has('fs.read', './veri/ayar.json'));
try {
  readFileSync('./veri/ayar.json', 'utf8');
} catch (err) {
  console.log('tekrar okuma:', err.code);
}
bash
$ node --permission --allow-fs-read=./veri digerleri.mjs
child: ERR_ACCESS_DENIED ChildProcess
worker: ERR_ACCESS_DENIED WorkerThreads
net: ERR_ACCESS_DENIED connect ERR_ACCESS_DENIED Access to this API has been restricted. Use --allow-net to manage permissions.
drop sonrası: false
tekrar okuma: ERR_ACCESS_DENIED

Alt süreç ve worker oluşturma, ağ bağlantısı daha açılmadan engellendi. Aynı betiği --allow-net --allow-child-process --allow-worker ile çalıştırdığımızda engeller kalktı ve fetch bu kez dinleyen bir sunucu olmadığı için ECONNREFUSED verdi; o hatanın anlamı sistem hataları yazısında. Bu çalıştırmada Node.js şu uyarıları da bastı:

bash
(node:91548) [PERM0002] SecurityWarning: The flag --allow-child-process must be used with extreme caution. It could invalidate the permission model.
(node:91548) [PERM0006] SecurityWarning: The flag --allow-worker must be used with extreme caution. It could invalidate the permission model.
(node:91548) ExperimentalWarning: The flag --allow-net is under experimental phase.

Uyarıların nedeni açık: izin verilen bir alt süreç, kısıtsız yeni bir Node.js süreci başlatarak modeli boşa çıkarabilir. Belgelere göre child_process.fork() izin bayraklarını çalıştırma argümanlarıyla, v24.4.0 itibarıyla spawn() da NODE_OPTIONS ortam değişkeniyle alt Node.js sürecine aktarır. Worker tarafında ise varsayılan bir worker ana sürecin bayraklarını alır, ama execArgv seçeneğini açıkça verirseniz (boş dizi dahil) devralınan bayraklar değişir ve izinler o listede yoksa korunmaz. Bu iki modülün kullanımı için child_process ve worker_threads rehberlerine bakın.

process.permission.has ve drop

has(kapsam, referans) çalışma zamanında izin sorgular; kapsam adları fs, fs.read, fs.write, child, worker, net, inspector, wasi, addon, ffi ve openssl.store değerleridir. Referans yalnızca dosya sistemi kapsamlarında anlam taşır. drop() ise izni geri dönüşü olmayacak şekilde bırakır. Yukarıdaki örnekte yapılandırmayı başlangıçta okuduk, sonra ./veri okuma iznini bıraktık; ikinci okuma reddedildi. Belgelere göre yalnızca açıkça verilmiş kaynağı bırakabilirsiniz: klasöre izin verdiyseniz içindeki tek bir dosyayı değil klasörün kendisini bırakmanız, joker ile verdiyseniz kapsamın tamamını bırakmanız gerekir. Bırakmak yalnızca gelecekteki kontrolleri etkiler; zaten açık dosya tanımlayıcıları, soketler ya da alt süreçler kapanmaz.

Mini örnek: dizin aşma hatasını izin modeliyle sınırlamak

İzin modelinin gerçek değeri, kodunuzdaki bir hatanın etkisini daraltmasıdır. Aşağıdaki küçük sunucu, sorgu parametresindeki dosya adını public klasörüyle birleştirip okuyor. Kodda bilerek bir açık bıraktık: ../ içeren bir ad klasörün dışına çıkabiliyor. Betik dinlemeye başlayınca kendisine üç istek atıp kapanıyor:

javascript
// sunucu.mjs
import { createServer } from 'node:http';
import { readFile, appendFile } from 'node:fs/promises';
import { join } from 'node:path';

const sunucu = createServer(async (req, res) => {
  const dosya = new URL(req.url, 'http://x').searchParams.get('dosya');
  try {
    const govde = await readFile(join('public', dosya), 'utf8');
    await appendFile('log/erisim.log', `${dosya} 200\n`);
    res.end(govde);
  } catch (err) {
    res.statusCode = err.code === 'ERR_ACCESS_DENIED' ? 403 : 404;
    res.end(err.code);
  }
});

sunucu.listen(0, '127.0.0.1', async () => {
  const { port } = sunucu.address();
  for (const d of ['index.txt', '../gizli/anahtar.txt', 'yok.txt']) {
    const r = await fetch(`http://127.0.0.1:${port}/?dosya=${encodeURIComponent(d)}`);
    console.log(d, r.status, await r.text());
  }
  sunucu.close();
});
bash
$ node sunucu.mjs
index.txt 200 Merhaba
../gizli/anahtar.txt 200 parola=1234
yok.txt 404 ENOENT

$ node --permission --allow-fs-read=./public --allow-fs-write=./log --allow-net sunucu.mjs
(node:17796) ExperimentalWarning: The flag --allow-net is under experimental phase.
(Use `node --trace-warnings ...` to show where the warning was created)
index.txt 200 Merhaba
../gizli/anahtar.txt 403 ERR_ACCESS_DENIED
yok.txt 404 ENOENT

İzin modeli olmadan saldırgan, parola dosyasını düz metin olarak okuyabildi. Aynı kod yalnızca public klasörünü okuyabilen, yalnızca log klasörüne yazabilen ve ağı kullanabilen bir süreçte çalıştığında istek ERR_ACCESS_DENIED ile reddedildi ve biz bunu 403 yanıtına çevirdik. Asıl düzeltme yine koddadır: birleştirilmiş yolun public içinde kaldığını path.resolve() ile doğrulamalısınız; bunun nasıl yapılacağı path modülü yazısında anlatılıyor. İzin modeli bu tür bir hatayı kapatmaz, ama kaçırdığınız bir hatanın zararını sınırlar. Sunucunun kendisi için node:http rehberine bakabilirsiniz.

Denetim modu: --permission-audit

Mevcut bir uygulamanın hangi izinlere ihtiyaç duyduğunu tahmin etmek zordur. v25.8.0 ile gelen --permission-audit bayrağı kontrolleri yapar ama erişimi engellemez; her ihlali node:diagnostics_channel üzerinden yayınlar. Dosya sistemi ihlalleri node:permission-model:fs kanalına, ağ ihlalleri node:permission-model:net kanalına gider; diğer kapsamların da kendi kanalı vardır:

javascript
// denetim.mjs
import diagnostics_channel from 'node:diagnostics_channel';
import { readFileSync } from 'node:fs';

diagnostics_channel.channel('node:permission-model:fs').subscribe((msg) => {
  console.log('engellenirdi:', msg.permission, msg.resource.split(/[\\/]/).slice(-2).join('/'));
});

const metin = readFileSync('./gizli/anahtar.txt', 'utf8');
console.log('okuma yine de çalıştı:', metin.length, 'karakter');
bash
$ node --permission-audit denetim.mjs
engellenirdi: FileSystemRead gizli/anahtar.txt
okuma yine de çalıştı: 11 karakter

Bu modda --permission vermeniz gerekmez. İkisini birlikte verirseniz --permission önceliklidir ve model engelleme modunda çalışır. Pratik bir akış şöyle olabilir: uygulamayı test ortamında denetim moduyla çalıştırıp kanallara gelen mesajları kaydedin, listeyi inceleyip gereken --allow-* bayraklarını belirleyin, sonra üretimde --permission ile başlatın. Olay tabanlı abonelik mantığı için EventEmitter rehberine göz atabilirsiniz.

İzinleri node.config.json dosyasına yazmak

Uzun bayrak listelerini komut satırında taşımak yerine yapılandırma dosyasına koyabilirsiniz. İzinler permission adlı üst düzey nesnenin içine yazılır; bu nesne varsa Node.js --permission bayrağını kendisi etkinleştirir:

json
{
  "permission": {
    "allow-fs-read": ["./veri"],
    "allow-fs-write": ["./veri"]
  }
}
bash
$ node --config-file uygulama.mjs
izin modeli açık mı: true
fs.read veri: true
fs.read gizli: false
fs.write: false
child: false
ayar okundu: { port: 3000 }
ERR_ACCESS_DENIED FileSystemRead gizli/anahtar.txt

Değer vermeden yazılan --config-file, çalışma dizinindeki node.config.json dosyasını arar; başka bir yol için --config-file=yol biçimini kullanın, boşlukla ayrılmış biçim desteklenmez. Bu kez ./veri klasörüne yazma izni verdiğimiz için log.txt hatasız oluşturuldu. Bayrak v26.12.0 ile --experimental-config-file adından bugünkü adına geçti; dosyanın diğer alanları NODE_OPTIONS ve config dosyası yazısında anlatılıyor.

Uygulama türüne göre izin seti kurmak

İzin listesini çıkarırken "en az yetki" ilkesini izleyin: süreç yalnızca işini yapmak için gerçekten gereken kaynaklara erişebilmeli. Birkaç yaygın senaryo için başlangıç noktası şöyle olabilir. Bir dosya dönüştürme aracı yalnızca girdi klasörünü okur ve çıktı klasörüne yazar; ona ağ ya da alt süreç izni vermenin bir nedeni yoktur. Bir web API'si ağ iznine, uygulama klasörünü okuma iznine ve belki yalnızca günlük ya da yükleme klasörüne yazma iznine ihtiyaç duyar. Derleme betikleri ise çoğu zaman başka programları çalıştırdığı için --allow-child-process ister; bu durumda yukarıda gördüğümüz güvenlik uyarısını ciddiye alın ve modelin alt süreçlerde ne kadar koruma sağlayabileceğini tartın.

Bağımlılıkların da izinlere ihtiyacı olabileceğini unutmayın. Bir paket ilk kullanımda önbellek dosyası yazmaya ya da yapılandırma okumaya çalışıyorsa hatayı ancak çalışma anında görürsünüz. Bu yüzden izin setini önce denetim modunda, gerçekçi bir iş yüküyle çalıştırarak doğrulamak; ardından engelleme moduna geçmek en az sürprizli yoldur. Yolları mümkün olduğunca dar tutun: --allow-fs-read=* yazmak modeli açmakla neredeyse aynıdır.

Modelin sınırları

Modelin sınırları: Dosya sistemi kısıtı node:fs modülüne uygulanır; node:sqlite gibi başka..., Sembolik bağlantılar izin verilen yolların dışına bile takip edilir; izin...

İzin modelini bir kum havuzu (sandbox) gibi düşünmemek için belgelerdeki kısıtları bilmek gerekir:

  • Dosya sistemi kısıtı node:fs modülüne uygulanır; node:sqlite gibi başka yollardan erişimi garanti altına almaz. Denememizde yazma izni olmayan gizli klasöründe new Database('./gizli/kayit.db') dosyayı oluşturabildi. Ayrıntı için node:sqlite rehberine bakın.
  • Sembolik bağlantılar izin verilen yolların dışına bile takip edilir; izin verdiğiniz yollarda göreli sembolik bağlantı bulunmamalıdır.
  • --env-file ve --openssl-config gibi bayraklar dosyaları ortam kurulmadan okur, bu yüzden modelin kurallarına tabi değildir. Ortam dosyası için --env-file yazısına bakabilirsiniz.
  • Var olan dosya tanımlayıcılarını node:fs üzerinden kullanmak modeli atlar.
  • process._debugProcess(pid) aynı kullanıcı altındaki başka bir Node.js sürecinin inspector'ını açabilir; bunu engellemek işletim sistemi düzeyinde yalıtımın işidir.
  • İzin modeli açıkken SQLite çalışma zamanı eklentileri yüklenemez ve OpenSSL motorları çalışma zamanında istenemez.

Bu yüzden güvenilmeyen kodu çalıştırmanız gerekiyorsa ayrı işletim sistemi kullanıcıları, konteyner ya da seccomp/AppArmor gibi katmanlar gerekir. Uygulamayı konteynerde çalıştırıyorsanız Dockerfile rehberi, systemd ile çalıştırıyorsanız systemd servis dosyası rehberi bu katmanları kurmanıza yardım eder; izin bayraklarını da ExecStart satırına ya da yapılandırma dosyasına ekleyebilirsiniz.

Sık yapılan hatalar

  • Modeli güvenlik sınırı sanmak: belgeler kötü niyetli koda karşı koruma sağlamadığını söyler; yalnızca güvenilen kodun kazalarını önler.
  • Virgülle yol ayırmak: --allow-fs-read=/a,/b artık desteklenmez; bayrağı tekrarlayın.
  • Var olmayan klasöre jokersiz izin vermek: klasör başlangıçta yoksa joker eklenmez; /yol/klasor/* yazın.
  • has() sorgusunu referanssız yorumlamak: has('fs.write') bütün dosya sistemini sorar; belirli bir yol için ikinci argüman verin.
  • --allow-child-process ile her şeyi açmak: alt süreç modeli boşa çıkarabilir; yalnızca gerçekten gerekiyorsa verin.
  • Modülleri kısıtlı ortamda mocklamak: test çalıştırıcısının modül mocklama özelliği izin modeliyle birlikte --allow-worker ister.
  • npx ile okuma iznini unutmak: paketin bulunduğu klasöre okuma izni vermezseniz FileSystemRead hatası alırsınız.

Sık Sorulan Sorular

İzin modeli npm paketlerinden gelen kötü kodu durdurur mu?

Hayır. Belgelere göre kötü niyetli kod modeli atlatabilir ve kısıtlar olmadan keyfi kod çalıştırabilir. Model, kendi kodunuzun ya da güvendiğiniz bağımlılıkların yanlışlıkla izin verilmeyen bir dosyaya yazmasını önlemek için tasarlanmıştır.

npx ile çalışan bir araca izin modeli uygulayabilir miyim?

Belgeler npx --node-options="--permission" paket-adi biçimini gösterir. Bu genellikle okuma hatası verir, çünkü Node.js paketi bulmak için diske erişmelidir; global node_modules ya da npm önbellek klasörüne --allow-fs-read ile izin vermeniz gerekir.

İzni sonradan geri verebilir miyim?

Hayır. process.permission.drop() geri alınamaz ve izin vermek için bir çalışma zamanı API'si yoktur. İzinler yalnızca süreç başlarken bayraklar ya da yapılandırma dosyasıyla verilir.

Hangi izinlere ihtiyacım olduğunu nasıl bulurum?

Uygulamayı --permission-audit ile çalıştırın ve node:permission-model:* kanallarına gelen mesajları kaydedin. Bu mod hiçbir erişimi engellemediği için gerçek iş akışınızı bozmadan eksik izinlerin listesini çıkarır.

Worker thread'ler ana sürecin izinlerini devralır mı?

Belgelere göre model worker'a kendiliğinden miras kalmaz; ancak execArgv vermeden oluşturulan bir worker ana sürecin komut satırı bayraklarını, dolayısıyla --permission ve --allow-* bayraklarını da alır. execArgv seçeneğini açıkça verirseniz bu liste devralınan bayrakların yerine geçer; izinlerin korunmasını istiyorsanız bayrakları o listeye yeniden yazmalısınız.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Node.js öğrenme rehberi · CLI bayrakları · child_process · node:sqlite · ERR_* hata kodları · Dockerfile rehberi