Prinsip keamanan least privilege — beri kode izin sesempat mungkin — mudah diucapkan dan sulit dijalankan: di JavaScript biasa, sebuah module yang kita impor bisa saja membaca seluruh direktori rumah, mengirim permintaan ke mana pun, atau menjalankan proses lain, dan tidak ada yang melarang. Permission Model mengubah arah default itu di tingkat runtime. Dengan satu flag, Node.js memulai proses dalam keadaan tidak boleh apa-apa — tidak membaca berkas, tidak menulis, tidak memanggil proses anak — lalu kita membuka akses satu per satu lewat daftar izin eksplisit. Chapter ini menghidupkan model tersebut, mengintipnya dari dalam lewat process.permission, dan menguji batasnya pada berkas serta proses anak.
Dimulai dari Tidak Ada #
Dua cara menjalankan Node yang sama menghasilkan dua dunia berbeda. Tanpa flag, semua API terbuka — dunia yang kita tempati sepanjang Course ini. Dengan node --permission, seluruh akses sensitif terkunci dan hanya terbuka untuk yang didaftarkan:
| Kebutuhan | Flag pembuka izin |
|---|---|
| Membaca berkas | --allow-fs-read=<jalur> |
| Menulis berkas | --allow-fs-write=<jalur> |
| Menjalankan proses anak | --allow-child-process |
| Membuat worker thread | --allow-worker |
| Semua flag izin lainnya | referensi Permission Model |
Nilai <jalur> bisa satu berkas (catatan.txt), satu folder (data/), atau beberapa sekaligus dengan koma (--allow-fs-read=a.txt,b.txt). Model ini hadir di Node.js sejak versi 20 dan statusnya stabil — tidak lagi eksperimental.
Bertanya kepada process.permission #
Sebuah API kecil memungkinkan program bertanya tentang izinnya sendiri — berguna untuk memberi pesan galat yang jujur ketika berjalan tanpa izin yang dibutuhkan:
const izin = process.permission;
if (izin === undefined) {
console.log('permission model tidak aktif - semua akses diizinkan');
} else {
console.log('fs.read semua :', izin.has('fs.read'));
console.log('fs.write semua :', izin.has('fs.write'));
console.log('baca catatan :', izin.has('fs.read', 'catatan.txt'));
console.log('tulis log :', izin.has('fs.write', 'log-aplikasi.txt'));
console.log('proses anak :', izin.has('child'));
}Tanpa model, process.permission bernilai undefined — penanda bahwa pertanyaan izin tidak relevan:
node izin-aktif.mjsOutput yang Dihasilkan:
permission model tidak aktif - semua akses diizinkanSekarang aktifkan model dengan dua izin sempit: boleh membaca catatan.txt dan menulis log-aplikasi.txt — tidak lebih:
node --permission --allow-fs-read=catatan.txt --allow-fs-write=log-aplikasi.txt izin-aktif.mjsOutput yang Dihasilkan:
fs.read semua : false
fs.write semua : false
baca catatan : true
tulis log : true
proses anak : falseBacalah keluarannya seperti kartu identitas akses: fs.read secara umum false — kita tidak boleh membaca sembarang berkas — namun fs.read untuk catatan.txt khusus true. Izin diberikan per jalur, bukan per kategori; begitu pula proses anak yang tetap tertutup karena tidak kita minta. Metode has() pada process.permission terdokumentasi di referensi process Node.js
.
Membaca dan Menulis Berkas #
Teori di atas kini diuji dengan operasi sungguhan. Program kecil ini menulis satu baris ke berkas log:
import { appendFile } from 'node:fs/promises';
try {
await appendFile('log-aplikasi.txt', 'satu baris catatan\n');
console.log('berhasil : catatan ditulis ke log-aplikasi.txt');
} catch (e) {
console.log('gagal :', e.code, '-', e.message);
}node --permission tulis-log.mjsOutput yang Dihasilkan:
gagal : ERR_ACCESS_DENIED - Access to this API has been restricted. Use --allow-fs-write to manage permissions.Gagal — dengan kode galat ERR_ACCESS_DENIED dan pesan yang menyebut flag penyelamatnya. Perhatikan bahwa proses tetap berakhir dengan rapi: galat akses dilempar sebagai Error biasa yang bisa ditangkap try/catch, bukan penghentian paksa. Bukalah izinnya:
node --permission --allow-fs-write=. tulis-log.mjsOutput yang Dihasilkan:
berhasil : catatan ditulis ke log-aplikasi.txtPenulis kini sah — izin . berarti seluruh folder kerja saat ini. Sisi pembacaan punya cerita yang sama; pembaca berikut mencoba membaca catatan.txt yang memang tersedia di bundle chapter:
import { readFile } from 'node:fs/promises';
try {
const isi = await readFile('catatan.txt', 'utf8');
console.log('terbaca :', isi.trim());
} catch (e) {
console.log('gagal :', e.code, '-', e.message);
}node --permission --allow-fs-write=. baca-catatan.mjsOutput yang Dihasilkan:
gagal : ERR_ACCESS_DENIED - Access to this API has been restricted. Use --allow-fs-read to manage permissions.Menulis boleh, membaca tetap tertutup — dua izin memang dua pintu terpisah. Tambahkan pembacaannya:
node --permission --allow-fs-read=. --allow-fs-write=. baca-catatan.mjsOutput yang Dihasilkan:
terbaca : catatan latihan untuk uji izin bacaProses Anak dan Peringatannya #
Kategori terakhir yang kita uji sering jadi pintu kecil yang diabaikan: node:child_process. Program ini menjalankan node --version sebagai proses anak:
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const jalankan = promisify(execFile);
try {
const { stdout } = await jalankan('node', ['--version']);
console.log('anak : proses anak menjalankan node', stdout.trim());
} catch (e) {
console.log('gagal :', e.code, '-', e.message);
}node --permission panggil-anak.mjsOutput yang Dihasilkan:
gagal : ERR_ACCESS_DENIED - Access to this API has been restricted. Use --allow-child-process to manage permissions.Tertolak seperti dua sebelumnya. Membukanya memungkin:
node --permission --allow-child-process panggil-anak.mjsOutput yang Dihasilkan:
anak : proses anak menjalankan node v24.21.0Proses anak berjalan dan melaporkan versi. Namun perhatikan 2>/dev/null pada perintah di atas: membuka izin ini menampilkan peringatan keamanan PERM0002 di stderr, yang menyala karena alasannya serius — proses anak yang kita izinkan tidak mewarisi daftar izin proses induknya. Sebuah proses anak bebas menjalankan apa pun, termasuk Node kedua tanpa --permission sama sekali, sehingga --allow-child-process secara efektif melubangi modelnya sendiri. Gunakan hanya ketika program memang tak tergantung padanya, dan biarkan peringatannya terbaca saat Anda menimbangnya — pada contoh di sini ia disembunyikan semata agar keluaran demonstrasi tetap bersih.
Batas Model #
Permission Model menjaga proses ini; ia bukan sandbox sistem operasi. Ia tidak membatasi penggunaan CPU dan memori, tidak mengenkripsi apa pun, dan tidak melindungi dari eksploitasi di dalam Node.js sendiri. Yang ia berikan adalah satu lapisan tegas di atas prinsip yang sudah kita kenal dari Section Security: input tidak dipercaya, akses tidak diberikan lebih dari kebutuhan. Dipadukan dengan deployment yang menjalankan proses sebagai pengguna sistem tanpa hak lebih — dan container yang membatasi lebih luas — model ini mengubah “kepercayaan” dari asumsi menjadi daftar yang bisa dibaca siapa pun dalam satu baris perintah.
Ada satu pertanyaan terakhir dari dunia deployment yang belum tersentuh section ini: bagaimana jika kita ingin membawa aplikasi ke mesin yang bahkan tidak memasang Node.js? Jawabannya memaketkan runtime bersama aplikasi — Single Executable Application, chapter berikutnya.